TestCreate exam almost completed
This commit is contained in:
parent
489226d5f5
commit
10620eae13
14 changed files with 228 additions and 22 deletions
|
@ -1,23 +1,37 @@
|
||||||
package models
|
package models
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"time"
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
"gorm.io/gorm"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type Exam struct {
|
type Exam struct {
|
||||||
ID string `gorm:"primaryKey"`
|
Meta
|
||||||
|
Participant *Participant
|
||||||
CreatedAt time.Time
|
Quizzes []*Quiz
|
||||||
UpdatedAt time.Time
|
}
|
||||||
DeletedAt gorm.DeletedAt `gorm:"index"`
|
|
||||||
|
func (e *Exam) String() string {
|
||||||
Name string
|
return fmt.Sprintf("Exam ID %v with %v quizzes.", e.ID, len(e.Quizzes))
|
||||||
Description string
|
}
|
||||||
|
|
||||||
Collection *Collection `gorm:"foreignKey:ID"`
|
func (e *Exam) GetID() string {
|
||||||
Participant []*Participant `gorm:"many2many:exam_participants"`
|
return e.ID
|
||||||
|
}
|
||||||
// Responses []string
|
|
||||||
|
func (e *Exam) SetID(id string) {
|
||||||
|
e.ID = id
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *Exam) GetHash() string {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *Exam) Marshal() ([]byte, error) {
|
||||||
|
return json.Marshal(e)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *Exam) Unmarshal(data []byte) error {
|
||||||
|
return json.Unmarshal(data, e)
|
||||||
}
|
}
|
||||||
|
|
5
store/exam.go
Normal file
5
store/exam.go
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
package store
|
||||||
|
|
||||||
|
import "git.andreafazzi.eu/andrea/probo/models"
|
||||||
|
|
||||||
|
type ExamStore = Store[*models.Exam]
|
|
@ -8,6 +8,7 @@ var (
|
||||||
DefaultCollectionsSubdir = "collections"
|
DefaultCollectionsSubdir = "collections"
|
||||||
DefaultParticipantsSubdir = "participants"
|
DefaultParticipantsSubdir = "participants"
|
||||||
DefaultGroupsSubdir = "groups"
|
DefaultGroupsSubdir = "groups"
|
||||||
|
DefaultExamsSubdir = "exams"
|
||||||
)
|
)
|
||||||
|
|
||||||
func GetDefaultQuizzesDir() string {
|
func GetDefaultQuizzesDir() string {
|
||||||
|
@ -25,3 +26,7 @@ func GetDefaultParticipantsDir() string {
|
||||||
func GetDefaultGroupsDir() string {
|
func GetDefaultGroupsDir() string {
|
||||||
return filepath.Join(DefaultBaseDir, DefaultGroupsSubdir)
|
return filepath.Join(DefaultBaseDir, DefaultGroupsSubdir)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func GetDefaultExamsDir() string {
|
||||||
|
return filepath.Join(DefaultBaseDir, DefaultExamsSubdir)
|
||||||
|
}
|
||||||
|
|
28
store/file/exam.go
Normal file
28
store/file/exam.go
Normal file
|
@ -0,0 +1,28 @@
|
||||||
|
package file
|
||||||
|
|
||||||
|
import (
|
||||||
|
"git.andreafazzi.eu/andrea/probo/models"
|
||||||
|
"git.andreafazzi.eu/andrea/probo/store"
|
||||||
|
)
|
||||||
|
|
||||||
|
type ExamFileStore = FileStore[*models.Exam, *store.Store[*models.Exam]]
|
||||||
|
|
||||||
|
func NewExamFileStore(config *FileStoreConfig[*models.Exam, *store.ExamStore]) (*ExamFileStore, error) {
|
||||||
|
return NewFileStore[*models.Exam](config, store.NewStore[*models.Exam]())
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewDefaultExamFileStore() (*ExamFileStore, error) {
|
||||||
|
return NewExamFileStore(
|
||||||
|
&FileStoreConfig[*models.Exam, *store.ExamStore]{
|
||||||
|
FilePathConfig: FilePathConfig{GetDefaultExamsDir(), "exam", ".json"},
|
||||||
|
IndexDirFunc: DefaultIndexDirFunc[*models.Exam, *store.ExamStore],
|
||||||
|
CreateEntityFunc: func() *models.Exam {
|
||||||
|
return &models.Exam{
|
||||||
|
Participant: &models.Participant{},
|
||||||
|
Quizzes: make([]*models.Quiz, 0),
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
}
|
111
store/file/exam_test.go
Normal file
111
store/file/exam_test.go
Normal file
|
@ -0,0 +1,111 @@
|
||||||
|
package file
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"git.andreafazzi.eu/andrea/probo/models"
|
||||||
|
"git.andreafazzi.eu/andrea/probo/store"
|
||||||
|
"github.com/remogatto/prettytest"
|
||||||
|
)
|
||||||
|
|
||||||
|
type examTestSuite struct {
|
||||||
|
prettytest.Suite
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *examTestSuite) TestCreate() {
|
||||||
|
participantStore, err := NewParticipantFileStore(
|
||||||
|
&FileStoreConfig[*models.Participant, *store.ParticipantStore]{
|
||||||
|
FilePathConfig: FilePathConfig{"testdata/exams/participants", "participant", ".json"},
|
||||||
|
IndexDirFunc: DefaultIndexDirFunc[*models.Participant, *store.ParticipantStore],
|
||||||
|
CreateEntityFunc: func() *models.Participant {
|
||||||
|
return &models.Participant{
|
||||||
|
Attributes: make(map[string]string),
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
t.Nil(err)
|
||||||
|
|
||||||
|
quizStore, err := NewQuizFileStore(
|
||||||
|
&FileStoreConfig[*models.Quiz, *store.QuizStore]{
|
||||||
|
FilePathConfig: FilePathConfig{"testdata/exams/quizzes", "quiz", ".md"},
|
||||||
|
IndexDirFunc: DefaultQuizIndexDirFunc,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
t.Nil(err)
|
||||||
|
|
||||||
|
if !t.Failed() {
|
||||||
|
t.Equal(3, len(participantStore.ReadAll()))
|
||||||
|
|
||||||
|
examStore, err := NewDefaultExamFileStore()
|
||||||
|
t.Nil(err)
|
||||||
|
|
||||||
|
if !t.Failed() {
|
||||||
|
g := new(models.Group)
|
||||||
|
c := new(models.Collection)
|
||||||
|
|
||||||
|
participants := participantStore.Storer.FilterInGroup(g, &models.ParticipantFilter{
|
||||||
|
Attributes: map[string]string{"class": "1 D LIN"},
|
||||||
|
})
|
||||||
|
|
||||||
|
quizzes := quizStore.Storer.FilterInCollection(c, &models.Filter{
|
||||||
|
Tags: []*models.Tag{
|
||||||
|
{Name: "#tag1"},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
for _, p := range participants {
|
||||||
|
e := new(models.Exam)
|
||||||
|
e.Participant = p
|
||||||
|
e.Quizzes = quizzes
|
||||||
|
|
||||||
|
_, err = examStore.Create(e)
|
||||||
|
t.Nil(err)
|
||||||
|
|
||||||
|
defer os.Remove(examStore.GetPath(e))
|
||||||
|
|
||||||
|
examFromDisk, err := readExamFromJSON(e.GetID())
|
||||||
|
t.Nil(err)
|
||||||
|
|
||||||
|
if !t.Failed() {
|
||||||
|
t.Not(t.Nil(examFromDisk.Participant))
|
||||||
|
if !t.Failed() {
|
||||||
|
t.Equal("Smith", examFromDisk.Participant.Lastname)
|
||||||
|
t.Equal(2, len(examFromDisk.Quizzes))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func readExamFromJSON(examID string) (*models.Exam, error) {
|
||||||
|
// Build the path to the JSON file
|
||||||
|
jsonPath := fmt.Sprintf("testdata/exams/exam_%s.json", examID)
|
||||||
|
|
||||||
|
// Open the JSON file
|
||||||
|
file, err := os.Open(jsonPath)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to open JSON file: %w", err)
|
||||||
|
}
|
||||||
|
defer file.Close()
|
||||||
|
|
||||||
|
// Read the JSON data from the file
|
||||||
|
jsonData, err := io.ReadAll(file)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to read JSON data: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Unmarshal the JSON data into an Exam object
|
||||||
|
var exam models.Exam
|
||||||
|
if err := json.Unmarshal(jsonData, &exam); err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to parse JSON data: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return &exam, nil
|
||||||
|
}
|
|
@ -86,7 +86,7 @@ func DefaultIndexDirFunc[T FileStorable, K Storer[T]](s *FileStore[T, K]) error
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
mEntity, err := s.Create(entity)
|
mEntity, err := s.Create(entity, fullPath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -116,20 +116,22 @@ func NewFileStore[T FileStorable, K Storer[T]](config *FileStoreConfig[T, K], st
|
||||||
return store, nil
|
return store, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *FileStore[T, K]) Create(entity T) (T, error) {
|
func (s *FileStore[T, K]) Create(entity T, path ...string) (T, error) {
|
||||||
e, err := s.Storer.Create(entity)
|
e, err := s.Storer.Create(entity)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return e, err
|
return e, err
|
||||||
}
|
}
|
||||||
|
|
||||||
filePath := filepath.Join(s.Dir, fmt.Sprintf("%s_%v%s", s.FilePrefix, e.GetID(), s.FileSuffix))
|
|
||||||
|
|
||||||
data, err := e.Marshal()
|
data, err := e.Marshal()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return e, err
|
return e, err
|
||||||
}
|
}
|
||||||
|
|
||||||
file, err := os.Create(filePath)
|
if len(path) == 0 {
|
||||||
|
path = append(path, filepath.Join(s.Dir, fmt.Sprintf("%s_%v%s", s.FilePrefix, e.GetID(), s.FileSuffix)))
|
||||||
|
}
|
||||||
|
|
||||||
|
file, err := os.Create(path[0])
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return e, err
|
return e, err
|
||||||
}
|
}
|
||||||
|
@ -141,7 +143,7 @@ func (s *FileStore[T, K]) Create(entity T) (T, error) {
|
||||||
return e, err
|
return e, err
|
||||||
}
|
}
|
||||||
|
|
||||||
s.SetPath(e, filePath)
|
s.SetPath(e, path[0])
|
||||||
|
|
||||||
return e, nil
|
return e, nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,5 +17,6 @@ func TestRunner(t *testing.T) {
|
||||||
new(collectionTestSuite),
|
new(collectionTestSuite),
|
||||||
new(participantTestSuite),
|
new(participantTestSuite),
|
||||||
new(groupTestSuite),
|
new(groupTestSuite),
|
||||||
|
new(examTestSuite),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
1
store/file/testdata/exams/exam_fe0a7ee0-f31a-413d-ba81-ab5068bc4c73.json
vendored
Normal file
1
store/file/testdata/exams/exam_fe0a7ee0-f31a-413d-ba81-ab5068bc4c73.json
vendored
Normal file
|
@ -0,0 +1 @@
|
||||||
|
{"id":"fe0a7ee0-f31a-413d-ba81-ab5068bc4c73","created_at":"0001-01-01T00:00:00Z","updated_at":"0001-01-01T00:00:00Z","Participant":{"ID":"1234","Firstname":"John","Lastname":"Smith","Token":111222,"Attributes":{"class":"1 D LIN"}},"Quizzes":[{"id":"0610939b-a1a3-4d0e-bbc4-2aae0e8ee4b9","created_at":"2023-11-27T17:51:53.910642221+01:00","updated_at":"0001-01-01T00:00:00Z","hash":"","question":{"id":"98c0eec9-677f-464e-9e3e-864a859f29a3","created_at":"0001-01-01T00:00:00Z","updated_at":"0001-01-01T00:00:00Z","text":"Question text with #tag1."},"answers":[{"id":"1ab5ff1f-74c8-4efc-abdc-d03a3640f3bc","text":"Answer 1"},{"id":"74547724-b905-476f-8cfc-6ee633f92ef3","text":"Answer 2"},{"id":"96c1a8ee-c50c-4ebc-89e4-9f3ca356adbd","text":"Answer 3"},{"id":"16c4b95e-64ce-4666-8cbe-b66fa59eb23b","text":"Answer 4"}],"tags":[{"CreatedAt":"0001-01-01T00:00:00Z","UpdatedAt":"0001-01-01T00:00:00Z","DeletedAt":null,"name":"#tag1"}],"correct":{"id":"1ab5ff1f-74c8-4efc-abdc-d03a3640f3bc","text":"Answer 1"},"CorrectPos":0,"type":0},{"id":"915818c4-b0ce-4efc-8def-7fc8d5fa7454","created_at":"2023-11-27T17:51:53.91077796+01:00","updated_at":"0001-01-01T00:00:00Z","hash":"","question":{"id":"70793f0d-2855-4140-814e-40167464424b","created_at":"0001-01-01T00:00:00Z","updated_at":"0001-01-01T00:00:00Z","text":"Another question text with #tag1."},"answers":[{"id":"1ab5ff1f-74c8-4efc-abdc-d03a3640f3bc","text":"Answer 1"},{"id":"74547724-b905-476f-8cfc-6ee633f92ef3","text":"Answer 2"},{"id":"96c1a8ee-c50c-4ebc-89e4-9f3ca356adbd","text":"Answer 3"},{"id":"16c4b95e-64ce-4666-8cbe-b66fa59eb23b","text":"Answer 4"}],"tags":[{"CreatedAt":"0001-01-01T00:00:00Z","UpdatedAt":"0001-01-01T00:00:00Z","DeletedAt":null,"name":"#tag1"}],"correct":{"id":"1ab5ff1f-74c8-4efc-abdc-d03a3640f3bc","text":"Answer 1"},"CorrectPos":0,"type":0}]}
|
1
store/file/testdata/exams/participants/jack.json
vendored
Normal file
1
store/file/testdata/exams/participants/jack.json
vendored
Normal file
|
@ -0,0 +1 @@
|
||||||
|
{"ID":"5467","Firstname":"Jack","Lastname":"Sparrow","Token":333444,"Attributes":{"class":"2 D LIN"}}
|
1
store/file/testdata/exams/participants/john.json
vendored
Normal file
1
store/file/testdata/exams/participants/john.json
vendored
Normal file
|
@ -0,0 +1 @@
|
||||||
|
{"ID":"1234","Firstname":"John","Lastname":"Smith","Token":111222,"Attributes":{"class":"1 D LIN"}}
|
1
store/file/testdata/exams/participants/wendy.json
vendored
Normal file
1
store/file/testdata/exams/participants/wendy.json
vendored
Normal file
|
@ -0,0 +1 @@
|
||||||
|
{"ID":"567812","Firstname":"Wendy","Lastname":"Darling","Token":333444,"Attributes":{"class":"2 D LIN"}}
|
12
store/file/testdata/exams/quizzes/quiz_1.md
vendored
Normal file
12
store/file/testdata/exams/quizzes/quiz_1.md
vendored
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
---
|
||||||
|
id: 0610939b-a1a3-4d0e-bbc4-2aae0e8ee4b9
|
||||||
|
created_at: 2023-11-27T17:51:53.910642221+01:00
|
||||||
|
updated_at: 0001-01-01T00:00:00Z
|
||||||
|
---
|
||||||
|
Question text with #tag1.
|
||||||
|
|
||||||
|
* Answer 1
|
||||||
|
* Answer 2
|
||||||
|
* Answer 3
|
||||||
|
* Answer 4
|
||||||
|
|
12
store/file/testdata/exams/quizzes/quiz_2.md
vendored
Normal file
12
store/file/testdata/exams/quizzes/quiz_2.md
vendored
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
---
|
||||||
|
id: 915818c4-b0ce-4efc-8def-7fc8d5fa7454
|
||||||
|
created_at: 2023-11-27T17:51:53.91077796+01:00
|
||||||
|
updated_at: 0001-01-01T00:00:00Z
|
||||||
|
---
|
||||||
|
Another question text with #tag1.
|
||||||
|
|
||||||
|
* Answer 1
|
||||||
|
* Answer 2
|
||||||
|
* Answer 3
|
||||||
|
* Answer 4
|
||||||
|
|
12
store/file/testdata/exams/quizzes/quiz_3.md
vendored
Normal file
12
store/file/testdata/exams/quizzes/quiz_3.md
vendored
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
---
|
||||||
|
id: 87e7825d-c75c-448b-b718-9c0b68407ab6
|
||||||
|
created_at: 2023-11-27T17:51:53.910886823+01:00
|
||||||
|
updated_at: 0001-01-01T00:00:00Z
|
||||||
|
---
|
||||||
|
Another question text with #tag2.
|
||||||
|
|
||||||
|
* Answer 1
|
||||||
|
* Answer 2
|
||||||
|
* Answer 3
|
||||||
|
* Answer 4
|
||||||
|
|
Loading…
Reference in a new issue