Bladeren bron

TestCreate exam almost completed

andrea 5 maanden geleden
bovenliggende
commit
10620eae13

+ 26 - 12
models/exam.go

@@ -1,23 +1,37 @@
 package models
 
 import (
-	"time"
-
-	"gorm.io/gorm"
+	"encoding/json"
+	"fmt"
 )
 
 type Exam struct {
-	ID string `gorm:"primaryKey"`
+	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
+}
 
-	CreatedAt time.Time
-	UpdatedAt time.Time
-	DeletedAt gorm.DeletedAt `gorm:"index"`
+func (e *Exam) SetID(id string) {
+	e.ID = id
+}
 
-	Name        string
-	Description string
+func (e *Exam) GetHash() string {
+	return ""
+}
 
-	Collection  *Collection    `gorm:"foreignKey:ID"`
-	Participant []*Participant `gorm:"many2many:exam_participants"`
+func (e *Exam) Marshal() ([]byte, error) {
+	return json.Marshal(e)
+
+}
 
-	//	Responses []string
+func (e *Exam) Unmarshal(data []byte) error {
+	return json.Unmarshal(data, e)
 }

+ 5 - 0
store/exam.go

@@ -0,0 +1,5 @@
+package store
+
+import "git.andreafazzi.eu/andrea/probo/models"
+
+type ExamStore = Store[*models.Exam]

+ 5 - 0
store/file/defaults.go

@@ -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 - 0
store/file/exam.go

@@ -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 - 0
store/file/exam_test.go

@@ -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
+}

+ 8 - 6
store/file/file.go

@@ -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
 }

+ 1 - 0
store/file/file_test.go

@@ -17,5 +17,6 @@ func TestRunner(t *testing.T) {
 		new(collectionTestSuite),
 		new(participantTestSuite),
 		new(groupTestSuite),
+		new(examTestSuite),
 	)
 }

+ 1 - 0
store/file/testdata/exams/exam_fe0a7ee0-f31a-413d-ba81-ab5068bc4c73.json

@@ -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 - 0
store/file/testdata/exams/participants/jack.json

@@ -0,0 +1 @@
+{"ID":"5467","Firstname":"Jack","Lastname":"Sparrow","Token":333444,"Attributes":{"class":"2 D LIN"}}

+ 1 - 0
store/file/testdata/exams/participants/john.json

@@ -0,0 +1 @@
+{"ID":"1234","Firstname":"John","Lastname":"Smith","Token":111222,"Attributes":{"class":"1 D LIN"}}

+ 1 - 0
store/file/testdata/exams/participants/wendy.json

@@ -0,0 +1 @@
+{"ID":"567812","Firstname":"Wendy","Lastname":"Darling","Token":333444,"Attributes":{"class":"2 D LIN"}}

+ 12 - 0
store/file/testdata/exams/quizzes/quiz_1.md

@@ -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 - 0
store/file/testdata/exams/quizzes/quiz_2.md

@@ -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 - 0
store/file/testdata/exams/quizzes/quiz_3.md

@@ -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
+