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
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"gorm.io/gorm"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
type Exam struct {
|
||||
ID string `gorm:"primaryKey"`
|
||||
|
||||
CreatedAt time.Time
|
||||
UpdatedAt time.Time
|
||||
DeletedAt gorm.DeletedAt `gorm:"index"`
|
||||
|
||||
Name string
|
||||
Description string
|
||||
|
||||
Collection *Collection `gorm:"foreignKey:ID"`
|
||||
Participant []*Participant `gorm:"many2many:exam_participants"`
|
||||
|
||||
// Responses []string
|
||||
Meta
|
||||
Participant *Participant
|
||||
Quizzes []*Quiz
|
||||
}
|
||||
|
||||
func (e *Exam) String() string {
|
||||
return fmt.Sprintf("Exam ID %v with %v quizzes.", e.ID, len(e.Quizzes))
|
||||
}
|
||||
|
||||
func (e *Exam) GetID() string {
|
||||
return e.ID
|
||||
}
|
||||
|
||||
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"
|
||||
DefaultParticipantsSubdir = "participants"
|
||||
DefaultGroupsSubdir = "groups"
|
||||
DefaultExamsSubdir = "exams"
|
||||
)
|
||||
|
||||
func GetDefaultQuizzesDir() string {
|
||||
|
@ -25,3 +26,7 @@ func GetDefaultParticipantsDir() string {
|
|||
func GetDefaultGroupsDir() string {
|
||||
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
|
||||
}
|
||||
|
||||
mEntity, err := s.Create(entity)
|
||||
mEntity, err := s.Create(entity, fullPath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -116,20 +116,22 @@ func NewFileStore[T FileStorable, K Storer[T]](config *FileStoreConfig[T, K], st
|
|||
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)
|
||||
if err != nil {
|
||||
return e, err
|
||||
}
|
||||
|
||||
filePath := filepath.Join(s.Dir, fmt.Sprintf("%s_%v%s", s.FilePrefix, e.GetID(), s.FileSuffix))
|
||||
|
||||
data, err := e.Marshal()
|
||||
if err != nil {
|
||||
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 {
|
||||
return e, err
|
||||
}
|
||||
|
@ -141,7 +143,7 @@ func (s *FileStore[T, K]) Create(entity T) (T, error) {
|
|||
return e, err
|
||||
}
|
||||
|
||||
s.SetPath(e, filePath)
|
||||
s.SetPath(e, path[0])
|
||||
|
||||
return e, nil
|
||||
}
|
||||
|
|
|
@ -17,5 +17,6 @@ func TestRunner(t *testing.T) {
|
|||
new(collectionTestSuite),
|
||||
new(participantTestSuite),
|
||||
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