Add Marshal/Unmarshal

This commit is contained in:
andrea 2023-11-20 14:14:09 +01:00
parent 3196982a64
commit ac95b38fe8
13 changed files with 313 additions and 363 deletions

View file

@ -1,5 +1,7 @@
package models package models
import "encoding/json"
type Filter struct { type Filter struct {
Tags []*Tag Tags []*Tag
} }
@ -28,3 +30,12 @@ func (c *Collection) SetID(id string) {
func (c *Collection) GetHash() string { func (c *Collection) GetHash() string {
return "" return ""
} }
func (c *Collection) Marshal() ([]byte, error) {
return json.Marshal(c)
}
func (c *Collection) Unmarshal(data []byte) error {
return json.Unmarshal(data, c)
}

View file

@ -42,11 +42,13 @@ Question text with #tag1 #tag2 (3).
CorrectPos: 0, CorrectPos: 0,
} }
quiz, _, err := MarkdownToQuiz(markdown) q := new(Quiz)
err := MarkdownToQuiz(q, markdown)
t.Nil(err, fmt.Sprintf("Quiz should be parsed without errors: %v", err)) t.Nil(err, fmt.Sprintf("Quiz should be parsed without errors: %v", err))
if !t.Failed() { if !t.Failed() {
t.True(reflect.DeepEqual(quiz, expectedQuiz), fmt.Sprintf("Expected %+v, got %+v", expectedQuiz, quiz)) t.True(reflect.DeepEqual(q, expectedQuiz), fmt.Sprintf("Expected %+v got %+v", expectedQuiz, q))
} }
} }

View file

@ -2,6 +2,7 @@ package models
import ( import (
"crypto/sha256" "crypto/sha256"
"encoding/json"
"fmt" "fmt"
"strings" "strings"
) )
@ -42,3 +43,12 @@ func (p *Participant) AttributesToSlice() []string {
return result return result
} }
func (p *Participant) Marshal() ([]byte, error) {
return json.Marshal(p)
}
func (p *Participant) Unmarshal(data []byte) error {
return json.Unmarshal(data, p)
}

View file

@ -23,10 +23,10 @@ type Quiz struct {
Type int `json:"type"` Type int `json:"type"`
} }
func MarkdownToQuiz(markdown string) (*Quiz, *Meta, error) { func MarkdownToQuiz(quiz *Quiz, markdown string) error {
meta, remainingMarkdown, err := ParseMetaHeaderFromMarkdown(markdown) meta, remainingMarkdown, err := ParseMetaHeaderFromMarkdown(markdown)
if err != nil { if err != nil {
return nil, nil, err return err
} }
lines := strings.Split(remainingMarkdown, "\n") lines := strings.Split(remainingMarkdown, "\n")
@ -50,21 +50,25 @@ func MarkdownToQuiz(markdown string) (*Quiz, *Meta, error) {
questionText = strings.TrimRight(questionText, "\n") questionText = strings.TrimRight(questionText, "\n")
if questionText == "" { if questionText == "" {
return nil, nil, fmt.Errorf("Question text should not be empty.") return fmt.Errorf("Question text should not be empty.")
} }
if len(answers) < 2 { if len(answers) < 2 {
return nil, nil, fmt.Errorf("Number of answers should be at least 2 but parsed answers are %d.", len(answers)) return fmt.Errorf("Number of answers should be at least 2 but parsed answers are %d.", len(answers))
} }
question := &Question{Text: questionText} question := &Question{Text: questionText}
quiz := &Quiz{Question: question, Answers: answers, CorrectPos: 0}
quiz.Question = question
quiz.Answers = answers
//quiz = &Quiz{Question: question, Answers: answers, CorrectPos: 0}
if meta != nil { if meta != nil {
quiz.Meta = *meta quiz.Meta = *meta
} }
return quiz, meta, nil return nil
} }
func QuizToMarkdown(quiz *Quiz) (string, error) { func QuizToMarkdown(quiz *Quiz) (string, error) {
@ -108,6 +112,17 @@ func (q *Quiz) GetHash() string {
return q.calculateHash() return q.calculateHash()
} }
func (q *Quiz) Marshal() ([]byte, error) {
result, err := QuizToMarkdown(q)
return []byte(result), err
}
func (q *Quiz) Unmarshal(data []byte) error {
return MarkdownToQuiz(q, string(data))
}
func (q *Quiz) calculateHash() string { func (q *Quiz) calculateHash() string {
result := make([]string, 0) result := make([]string, 0)

View file

@ -1,54 +1,22 @@
package file package file
import ( import (
"encoding/json"
"os"
"path/filepath"
"git.andreafazzi.eu/andrea/probo/models" "git.andreafazzi.eu/andrea/probo/models"
"git.andreafazzi.eu/andrea/probo/store" "git.andreafazzi.eu/andrea/probo/store"
) )
type CollectionFileStore = FileStore[*models.Collection, *store.Store[*models.Collection]] type CollectionFileStore = FileStore[*models.Collection, *store.Store[*models.Collection]]
var DefaultCollectionDir = filepath.Join(BaseDir, CollectionsDir)
func NewCollectionFileStore(config *FileStoreConfig[*models.Collection, *store.CollectionStore]) (*CollectionFileStore, error) { func NewCollectionFileStore(config *FileStoreConfig[*models.Collection, *store.CollectionStore]) (*CollectionFileStore, error) {
return NewFileStore[*models.Collection](config, store.NewStore[*models.Collection]()) return NewFileStore[*models.Collection](config, store.NewStore[*models.Collection]())
} }
func DefaultUnmarshalCollectionFunc(s *store.Store[*models.Collection], filepath string, content []byte) (*models.Collection, error) { func NewDefaultCollectionFileStore() (*CollectionFileStore, error) {
collection := new(models.Collection) return NewCollectionFileStore(
err := json.Unmarshal(content, &collection) &FileStoreConfig[*models.Collection, *store.CollectionStore]{
if err != nil { FilePathConfig: FilePathConfig{DefaultBaseDir, "collection", ".json"},
return nil, err IndexDirFunc: DefaultIndexDirFunc[*models.Collection, *store.CollectionStore],
} },
)
c, err := s.Create(collection)
if err != nil {
return nil, err
}
return c, nil
}
func DefaultMarshalCollectionFunc(s *store.Store[*models.Collection], filePath string, collection *models.Collection) error {
jsonData, err := json.Marshal(collection)
if err != nil {
return err
}
file, err := os.Create(filePath)
if err != nil {
return err
}
defer file.Close()
_, err = file.Write(jsonData)
if err != nil {
return err
}
return nil
} }

View file

@ -48,14 +48,7 @@ func (t *collectionTestSuite) TestCreateCollection() {
}, },
}) })
store, err := NewCollectionFileStore( store, err := NewDefaultCollectionFileStore()
&FileStoreConfig[*models.Collection, *store.CollectionStore]{
FilePathConfig: FilePathConfig{"testdata", "collection", ".json"},
IndexDirFunc: DefaultIndexDirFunc[*models.Collection, *store.CollectionStore],
UnmarshalFunc: DefaultUnmarshalCollectionFunc,
MarshalFunc: DefaultMarshalCollectionFunc,
},
)
t.Nil(err) t.Nil(err)
c := new(models.Collection) c := new(models.Collection)

22
store/file/defaults.go Normal file
View file

@ -0,0 +1,22 @@
package file
import "path/filepath"
var (
DefaultBaseDir = "data"
DefaultQuizzesSubdir = "quizzes"
DefaultCollectionsSubdir = "collections"
DefaultParticipantsSubdir = "participants"
)
func GetDefaultQuizzesDir() string {
return filepath.Join(DefaultBaseDir, DefaultQuizzesSubdir)
}
func GetDefaultCollectionsDir() string {
return filepath.Join(DefaultBaseDir, DefaultCollectionsSubdir)
}
func GetDefaultParticipantsDir() string {
return filepath.Join(DefaultBaseDir, DefaultParticipantsSubdir)
}

View file

@ -1,7 +1,6 @@
package file package file
import ( import (
"encoding/json"
"errors" "errors"
"fmt" "fmt"
"io/fs" "io/fs"
@ -13,17 +12,19 @@ import (
"git.andreafazzi.eu/andrea/probo/store" "git.andreafazzi.eu/andrea/probo/store"
) )
type IndexDirFunc[T store.Storable, K Storer[T]] func(s *FileStore[T, K]) error type IndexDirFunc[T FileStorable, K Storer[T]] func(s *FileStore[T, K]) error
var ( var (
ErrorMetaHeaderIsNotPresent = errors.New("Meta header was not found in file.") ErrorMetaHeaderIsNotPresent = errors.New("Meta header was not found in file.")
BaseDir = "data"
QuizzesDir = "quizzes"
CollectionsDir = "collections"
ParticipantsDir = "participants"
) )
type FileStorable interface {
store.Storable
Marshal() ([]byte, error)
Unmarshal([]byte) error
}
type Storer[T store.Storable] interface { type Storer[T store.Storable] interface {
store.Storer[T] store.Storer[T]
} }
@ -34,16 +35,13 @@ type FilePathConfig struct {
FileSuffix string FileSuffix string
} }
type FileStoreConfig[T store.Storable, K Storer[T]] struct { type FileStoreConfig[T FileStorable, K Storer[T]] struct {
FilePathConfig FilePathConfig
FilepathFunc func(T, *FilePathConfig) string
UnmarshalFunc func(K, string, []byte) (T, error)
MarshalFunc func(K, string, T) error
IndexDirFunc func(*FileStore[T, K]) error IndexDirFunc func(*FileStore[T, K]) error
NoIndexOnCreate bool NoIndexOnCreate bool
} }
type FileStore[T store.Storable, K Storer[T]] struct { type FileStore[T FileStorable, K Storer[T]] struct {
*FileStoreConfig[T, K] *FileStoreConfig[T, K]
Storer K Storer K
@ -52,43 +50,7 @@ type FileStore[T store.Storable, K Storer[T]] struct {
paths map[string]string paths map[string]string
} }
func DefaultJSONUnmarshalFunc[T store.Storable, K Storer[T]](s K, filepath string, content []byte) (T, error) { func DefaultIndexDirFunc[T FileStorable, K Storer[T]](s *FileStore[T, K]) error {
entity := new(T)
err := json.Unmarshal(content, &entity)
if err != nil {
return *entity, err
}
c, err := s.Create(*entity)
if err != nil {
return c, err
}
return c, nil
}
func DefaultJSONMarshalFunc[T store.Storable, K Storer[T]](s K, filePath string, entity T) error {
jsonData, err := json.Marshal(entity)
if err != nil {
return err
}
file, err := os.Create(filePath)
if err != nil {
return err
}
defer file.Close()
_, err = file.Write(jsonData)
if err != nil {
return err
}
return nil
}
func DefaultIndexDirFunc[T store.Storable, K Storer[T]](s *FileStore[T, K]) error {
files, err := os.ReadDir(s.Dir) files, err := os.ReadDir(s.Dir)
if err != nil { if err != nil {
return err return err
@ -112,19 +74,26 @@ func DefaultIndexDirFunc[T store.Storable, K Storer[T]](s *FileStore[T, K]) erro
return err return err
} }
entity, err := s.UnmarshalFunc(s.Storer, fullPath, content) var entity T
err = entity.Unmarshal(content)
if err != nil { if err != nil {
return err return err
} }
s.SetPath(entity, fullPath) mEntity, err := s.Create(entity)
if err != nil {
return err
}
s.SetPath(mEntity, fullPath)
} }
return nil return nil
} }
func NewFileStore[T store.Storable, K Storer[T]](config *FileStoreConfig[T, K], storer K) (*FileStore[T, K], error) { func NewFileStore[T FileStorable, K Storer[T]](config *FileStoreConfig[T, K], storer K) (*FileStore[T, K], error) {
store := &FileStore[T, K]{ store := &FileStore[T, K]{
FileStoreConfig: config, FileStoreConfig: config,
Storer: storer, Storer: storer,
@ -148,15 +117,21 @@ func (s *FileStore[T, K]) Create(entity T) (T, error) {
return e, err return e, err
} }
var filePath string filePath := filepath.Join(s.Dir, fmt.Sprintf("%s_%v%s", s.FilePrefix, e.GetID(), s.FileSuffix))
if s.FilepathFunc == nil { data, err := e.Marshal()
filePath = filepath.Join(s.Dir, fmt.Sprintf("%s_%v%s", s.FilePrefix, e.GetID(), s.FileSuffix)) if err != nil {
} else { return e, err
filePath = s.FilepathFunc(entity, &s.FilePathConfig)
} }
err = s.MarshalFunc(s.Storer, filePath, e) file, err := os.Create(filePath)
if err != nil {
return e, err
}
defer file.Close()
_, err = file.Write(data)
if err != nil { if err != nil {
return e, err return e, err
} }
@ -174,7 +149,19 @@ func (s *FileStore[T, K]) Update(entity T, id string) (T, error) {
filePath := s.GetPath(e) filePath := s.GetPath(e)
err = s.MarshalFunc(s.Storer, filePath, e) data, err := e.Marshal()
if err != nil {
return e, err
}
file, err := os.Create(filePath)
if err != nil {
return e, err
}
defer file.Close()
_, err = file.Write(data)
if err != nil { if err != nil {
return e, err return e, err
} }

View file

@ -9,6 +9,8 @@ import (
var testdataDir = "./testdata" var testdataDir = "./testdata"
func TestRunner(t *testing.T) { func TestRunner(t *testing.T) {
DefaultBaseDir = "testdata"
prettytest.Run( prettytest.Run(
t, t,
new(quizTestSuite), new(quizTestSuite),

View file

@ -1,9 +1,6 @@
package file package file
import ( import (
"encoding/json"
"os"
"git.andreafazzi.eu/andrea/probo/models" "git.andreafazzi.eu/andrea/probo/models"
"git.andreafazzi.eu/andrea/probo/store" "git.andreafazzi.eu/andrea/probo/store"
) )
@ -13,39 +10,3 @@ type ParticipantFileStore = FileStore[*models.Participant, *store.Store[*models.
func NewParticipantFileStore(config *FileStoreConfig[*models.Participant, *store.Store[*models.Participant]]) (*ParticipantFileStore, error) { func NewParticipantFileStore(config *FileStoreConfig[*models.Participant, *store.Store[*models.Participant]]) (*ParticipantFileStore, error) {
return NewFileStore[*models.Participant](config, store.NewStore[*models.Participant]()) return NewFileStore[*models.Participant](config, store.NewStore[*models.Participant]())
} }
func DefaultUnmarshalParticipantFunc(s *store.Store[*models.Participant], filepath string, content []byte) (*models.Participant, error) {
participant := new(models.Participant)
err := json.Unmarshal(content, &participant)
if err != nil {
return nil, err
}
c, err := s.Create(participant)
if err != nil {
return nil, err
}
return c, nil
}
func DefaultMarshalParticipantFunc(s *store.Store[*models.Participant], filePath string, participant *models.Participant) error {
jsonData, err := json.Marshal(participant)
if err != nil {
return err
}
file, err := os.Create(filePath)
if err != nil {
return err
}
defer file.Close()
_, err = file.Write(jsonData)
if err != nil {
return err
}
return nil
}

View file

@ -18,8 +18,6 @@ func (t *participantTestSuite) TestCreate() {
&FileStoreConfig[*models.Participant, *store.ParticipantStore]{ &FileStoreConfig[*models.Participant, *store.ParticipantStore]{
FilePathConfig: filePathConfig, FilePathConfig: filePathConfig,
IndexDirFunc: DefaultIndexDirFunc[*models.Participant, *store.ParticipantStore], IndexDirFunc: DefaultIndexDirFunc[*models.Participant, *store.ParticipantStore],
UnmarshalFunc: DefaultJSONUnmarshalFunc[*models.Participant, *store.ParticipantStore],
MarshalFunc: DefaultJSONMarshalFunc[*models.Participant, *store.ParticipantStore],
}) })
t.Nil(err) t.Nil(err)

View file

@ -5,7 +5,9 @@ import (
"bytes" "bytes"
"errors" "errors"
"io" "io"
"io/fs"
"os" "os"
"path/filepath"
"strings" "strings"
"time" "time"
@ -20,53 +22,66 @@ func NewQuizFileStore(config *FileStoreConfig[*models.Quiz, *store.QuizStore]) (
return NewFileStore[*models.Quiz, *store.QuizStore](config, store.NewQuizStore()) return NewFileStore[*models.Quiz, *store.QuizStore](config, store.NewQuizStore())
} }
func DefaultUnmarshalQuizFunc(s *store.QuizStore, filepath string, content []byte) (*models.Quiz, error) { func NewDefaultQuizFileStore() (*QuizFileStore, error) {
quiz, meta, err := models.MarkdownToQuiz(string(content)) return NewQuizFileStore(
if err != nil { &FileStoreConfig[*models.Quiz, *store.QuizStore]{
return nil, err FilePathConfig: FilePathConfig{GetDefaultQuizzesDir(), "quiz", ".md"},
} IndexDirFunc: DefaultQuizIndexDirFunc,
},
)
var errQuizAlreadyPresent *store.ErrQuizAlreadyPresent
q, err := s.Create(quiz)
if err != nil && !errors.As(err, &errQuizAlreadyPresent) {
return nil, err
}
if meta == nil {
writeQuizHeader(filepath, &models.Meta{
ID: q.ID,
CreatedAt: time.Now(),
})
}
return q, nil
} }
func DefaultMarshalQuizFunc(s *store.QuizStore, filePath string, quiz *models.Quiz) error { func DefaultQuizIndexDirFunc(s *QuizFileStore) error {
markdown, err := models.QuizToMarkdown(quiz) files, err := os.ReadDir(s.Dir)
if err != nil { if err != nil {
return err return err
} }
file, err := os.Create(filePath) entityFiles := make([]fs.DirEntry, 0)
if err != nil {
return err for _, file := range files {
filename := file.Name()
if !file.IsDir() && strings.HasSuffix(filename, s.FileSuffix) {
entityFiles = append(entityFiles, file)
}
} }
defer file.Close() for _, file := range entityFiles {
filename := file.Name()
fullPath := filepath.Join(s.Dir, filename)
markdownWithMetaHeader, err := addMetaHeaderToMarkdown(markdown, &quiz.Meta) content, err := os.ReadFile(fullPath)
if err != nil { if err != nil {
return err return err
} }
_, err = file.Write([]byte(markdownWithMetaHeader)) var entity = new(models.Quiz)
if err != nil {
return err err = entity.Unmarshal(content)
if err != nil {
return err
}
var errQuizAlreadyPresent *store.ErrQuizAlreadyPresent
mEntity, err := s.Create(entity)
if err != nil && !errors.As(err, &errQuizAlreadyPresent) {
return err
}
if entity.ID == "" {
writeQuizHeader(fullPath, &models.Meta{
ID: mEntity.ID,
CreatedAt: time.Now(),
})
}
s.SetPath(mEntity, fullPath)
} }
return nil return nil
} }
func writeQuizHeader(path string, meta *models.Meta) (*models.Meta, error) { func writeQuizHeader(path string, meta *models.Meta) (*models.Meta, error) {

View file

@ -5,8 +5,6 @@ import (
"os" "os"
"path/filepath" "path/filepath"
"git.andreafazzi.eu/andrea/probo/models"
"git.andreafazzi.eu/andrea/probo/store"
"github.com/remogatto/prettytest" "github.com/remogatto/prettytest"
) )
@ -15,15 +13,7 @@ type quizTestSuite struct {
} }
func (t *quizTestSuite) TestReadAll() { func (t *quizTestSuite) TestReadAll() {
filePathConfig := FilePathConfig{"testdata/quizzes", "quiz", ".md"} store, err := NewDefaultQuizFileStore()
store, err := NewQuizFileStore(
&FileStoreConfig[*models.Quiz, *store.QuizStore]{
FilePathConfig: filePathConfig,
IndexDirFunc: DefaultIndexDirFunc[*models.Quiz, *store.QuizStore],
UnmarshalFunc: DefaultUnmarshalQuizFunc,
MarshalFunc: DefaultMarshalQuizFunc,
},
)
t.Nil(err) t.Nil(err)
if !t.Failed() { if !t.Failed() {
@ -38,198 +28,174 @@ func (t *quizTestSuite) TestReadAll() {
), ),
) )
files, _ := os.ReadDir(GetDefaultQuizzesDir())
t.Equal(5, len(files))
_, err = removeQuizHeader(filepath.Join(store.Dir, "quiz_5.md")) _, err = removeQuizHeader(filepath.Join(store.Dir, "quiz_5.md"))
} }
} }
func (t *quizTestSuite) TestCreate() { // func (t *quizTestSuite) TestCreate() {
filePathConfig := FilePathConfig{"testdata/quizzes", "quiz", ".md"} // store, err := NewDefaultQuizFileStore()
store, err := NewQuizFileStore( // t.Nil(err)
&FileStoreConfig[*models.Quiz, *store.QuizStore]{
FilePathConfig: filePathConfig,
IndexDirFunc: DefaultIndexDirFunc[*models.Quiz, *store.QuizStore],
UnmarshalFunc: DefaultUnmarshalQuizFunc,
MarshalFunc: DefaultMarshalQuizFunc,
NoIndexOnCreate: true,
},
)
t.Nil(err)
if !t.Failed() { // if !t.Failed() {
quiz, err := store.Create( // quiz, err := store.Create(
&models.Quiz{ // &models.Quiz{
Question: &models.Question{Text: "Newly created question text with #tag1 #tag2."}, // Question: &models.Question{Text: "Newly created question text with #tag1 #tag2."},
Answers: []*models.Answer{ // Answers: []*models.Answer{
{Text: "Answer 1"}, // {Text: "Answer 1"},
{Text: "Answer 2"}, // {Text: "Answer 2"},
{Text: "Answer 3"}, // {Text: "Answer 3"},
{Text: "Answer 4"}, // {Text: "Answer 4"},
}, // },
CorrectPos: 0, // CorrectPos: 0,
}) // })
t.Nil(err) // t.Nil(err)
t.Equal(2, len(quiz.Tags)) // t.Equal(2, len(quiz.Tags))
if !t.Failed() { // if !t.Failed() {
path := store.GetPath(quiz) // path := store.GetPath(quiz)
t.True(path != "", "Path should not be empty.") // t.True(path != "", "Path should not be empty.")
exists, err := os.Stat(path) // exists, err := os.Stat(path)
t.Nil(err) // t.Nil(err)
if !t.Failed() { // if !t.Failed() {
t.True(exists != nil, "The new quiz file was not created.") // t.True(exists != nil, "The new quiz file was not created.")
if !t.Failed() { // if !t.Failed() {
quizFromDisk, _, err := readQuizFromDisk(path) // quizFromDisk, err := readQuizFromDisk(path)
defer os.Remove(path) // defer os.Remove(path)
quizFromDisk.Correct = quiz.Answers[0] // quizFromDisk.Correct = quiz.Answers[0]
quizFromDisk.Tags = quiz.Tags // quizFromDisk.Tags = quiz.Tags
t.Nil(err) // t.Nil(err)
if !t.Failed() { // if !t.Failed() {
t.Equal(quizFromDisk.Question.Text, quiz.Question.Text) // t.Equal(quizFromDisk.Question.Text, quiz.Question.Text)
for i, a := range quizFromDisk.Answers { // for i, a := range quizFromDisk.Answers {
t.Equal(a.Text, quiz.Answers[i].Text) // t.Equal(a.Text, quiz.Answers[i].Text)
} // }
for i, tag := range quizFromDisk.Tags { // for i, tag := range quizFromDisk.Tags {
t.Equal(tag.Name, quiz.Tags[i].Name) // t.Equal(tag.Name, quiz.Tags[i].Name)
} // }
} // }
} // }
} // }
} // }
} // }
} // }
func (t *quizTestSuite) TestDelete() { // func (t *quizTestSuite) TestDelete() {
filePathConfig := FilePathConfig{"testdata/quizzes", "quiz", ".md"} // store, err := NewDefaultQuizFileStore()
store, err := NewQuizFileStore( // t.Nil(err)
&FileStoreConfig[*models.Quiz, *store.QuizStore]{
FilePathConfig: filePathConfig,
IndexDirFunc: DefaultIndexDirFunc[*models.Quiz, *store.QuizStore],
UnmarshalFunc: DefaultUnmarshalQuizFunc,
MarshalFunc: DefaultMarshalQuizFunc,
NoIndexOnCreate: true,
},
)
t.Nil(err)
if !t.Failed() { // if !t.Failed() {
quiz, err := store.Create( // quiz, err := store.Create(
&models.Quiz{ // &models.Quiz{
Question: &models.Question{Text: "This quiz should be deleted."}, // Question: &models.Question{Text: "This quiz should be deleted."},
Answers: []*models.Answer{ // Answers: []*models.Answer{
{Text: "Answer 1"}, // {Text: "Answer 1"},
{Text: "Answer 2"}, // {Text: "Answer 2"},
{Text: "Answer 3"}, // {Text: "Answer 3"},
{Text: "Answer 4"}, // {Text: "Answer 4"},
}, // },
CorrectPos: 0, // CorrectPos: 0,
}) // })
t.Nil(err) // t.Nil(err)
if !t.Failed() { // if !t.Failed() {
path := store.GetPath(quiz) // path := store.GetPath(quiz)
_, err := store.Delete(quiz.ID) // _, err := store.Delete(quiz.ID)
t.Nil(err, fmt.Sprintf("Quiz should be deleted without errors: %v", err)) // t.Nil(err, fmt.Sprintf("Quiz should be deleted without errors: %v", err))
if !t.Failed() { // if !t.Failed() {
_, err := os.Stat(path) // _, err := os.Stat(path)
t.Not(t.Nil(err)) // t.Not(t.Nil(err))
} // }
} // }
} // }
} // }
func (t *quizTestSuite) TestUpdate() { // func (t *quizTestSuite) TestUpdate() {
filePathConfig := FilePathConfig{"testdata/quizzes", "quiz", ".md"} // store, err := NewDefaultQuizFileStore()
store, err := NewQuizFileStore( // t.Nil(err)
&FileStoreConfig[*models.Quiz, *store.QuizStore]{
FilePathConfig: filePathConfig,
IndexDirFunc: DefaultIndexDirFunc[*models.Quiz, *store.QuizStore],
UnmarshalFunc: DefaultUnmarshalQuizFunc,
MarshalFunc: DefaultMarshalQuizFunc,
NoIndexOnCreate: true,
},
)
t.Nil(err)
if !t.Failed() { // if !t.Failed() {
quiz, err := store.Create( // quiz, err := store.Create(
&models.Quiz{ // &models.Quiz{
Question: &models.Question{Text: "Newly created question text with #tag1 #tag2."}, // Question: &models.Question{Text: "Newly created question text with #tag1 #tag2."},
Answers: []*models.Answer{ // Answers: []*models.Answer{
{Text: "Answer 1"}, // {Text: "Answer 1"},
{Text: "Answer 2"}, // {Text: "Answer 2"},
{Text: "Answer 3"}, // {Text: "Answer 3"},
{Text: "Answer 4"}, // {Text: "Answer 4"},
}, // },
CorrectPos: 0, // CorrectPos: 0,
}) // })
t.Nil(err) // t.Nil(err)
_, err = store.Update(&models.Quiz{ // _, err = store.Update(&models.Quiz{
Question: &models.Question{Text: "Newly created question text with #tag1 #tag2 #tag3."}, // Question: &models.Question{Text: "Newly created question text with #tag1 #tag2 #tag3."},
Answers: []*models.Answer{ // Answers: []*models.Answer{
{Text: "Answer 1"}, // {Text: "Answer 1"},
{Text: "Answer 2"}, // {Text: "Answer 2"},
{Text: "Answer 3"}, // {Text: "Answer 3"},
{Text: "Answer 4"}, // {Text: "Answer 4"},
}, // },
CorrectPos: 1, // CorrectPos: 1,
}, quiz.ID) // }, quiz.ID)
t.Nil(err) // t.Nil(err)
updatedQuizFromMemory, err := store.Read(quiz.ID) // updatedQuizFromMemory, err := store.Read(quiz.ID)
t.Equal(len(updatedQuizFromMemory.Tags), 3) // t.Equal(len(updatedQuizFromMemory.Tags), 3)
t.Equal("Answer 2", updatedQuizFromMemory.Correct.Text) // t.Equal("Answer 2", updatedQuizFromMemory.Correct.Text)
defer os.Remove(store.GetPath(quiz)) // defer os.Remove(store.GetPath(quiz))
} // }
} // }
func (t *quizTestSuite) TestAutowriteHeader() { // func (t *quizTestSuite) TestAutowriteHeader() {
filePathConfig := FilePathConfig{"testdata/quizzes", "quiz", ".md"} // store, err := NewDefaultQuizFileStore()
store, err := NewQuizFileStore( // t.Nil(err)
&FileStoreConfig[*models.Quiz, *store.QuizStore]{
FilePathConfig: filePathConfig,
IndexDirFunc: DefaultIndexDirFunc[*models.Quiz, *store.QuizStore],
UnmarshalFunc: DefaultUnmarshalQuizFunc,
MarshalFunc: DefaultMarshalQuizFunc,
},
)
t.Nil(err)
if !t.Failed() { // if !t.Failed() {
meta, err := readQuizHeader(filepath.Join(store.Dir, "quiz_5.md")) // meta, err := readQuizHeader(filepath.Join(store.Dir, "quiz_5.md"))
t.Nil(err) // t.Nil(err)
if !t.Failed() { // if !t.Failed() {
t.Not(t.Nil(meta)) // t.Not(t.Nil(meta))
if !t.Failed() { // if !t.Failed() {
t.True(meta.ID != "", "ID should not be empty") // t.True(meta.ID != "", "ID should not be empty")
if !t.Failed() { // if !t.Failed() {
_, err = removeQuizHeader(filepath.Join(store.Dir, "quiz_5.md")) // _, err = removeQuizHeader(filepath.Join(store.Dir, "quiz_5.md"))
t.True(err == nil) // t.True(err == nil)
} // }
} // }
} // }
} // }
} // }
func readQuizFromDisk(path string) (*models.Quiz, *models.Meta, error) { // func readQuizFromDisk(path string) (*models.Quiz, error) {
content, err := os.ReadFile(path) // content, err := os.ReadFile(path)
if err != nil { // if err != nil {
return nil, nil, err // return nil, err
} // }
return models.MarkdownToQuiz(string(content))
} // result := new(models.Quiz)
// err = result.Unmarshal(content)
// if err != nil {
// return nil, err
// }
// return result, nil
// }