diff --git a/pkg/models/document_group_review.go b/pkg/models/document_group_review.go new file mode 100644 index 000000000..9b3582307 --- /dev/null +++ b/pkg/models/document_group_review.go @@ -0,0 +1,159 @@ +package models + +import ( + "fmt" + "time" + + validation "github.com/go-ozzo/ozzo-validation/v4" + "gorm.io/gorm" + "gorm.io/gorm/clause" +) + +type DocumentGroupReview struct { + CreatedAt time.Time + UpdatedAt time.Time + DeletedAt gorm.DeletedAt `gorm:"index"` + + DocumentID uint `gorm:"primaryKey"` + Document Document + GroupID uint `gorm:"primaryKey"` + Group Group +} + +// DocumentReviews is a slice of document reviews. +type DocumentGroupReviews []DocumentGroupReview + +// BeforeSave is a hook to find or create associations before saving. +func (d *DocumentGroupReview) BeforeSave(tx *gorm.DB) error { + // Validate required fields. + if err := validation.ValidateStruct(&d.Document, + validation.Field( + &d.Document.GoogleFileID, validation.Required), + ); err != nil { + return err + } + if err := validation.ValidateStruct(&d.Group, + validation.Field( + &d.Group.EmailAddress, validation.Required), + ); err != nil { + return err + } + + if err := d.getAssociations(tx); err != nil { + return fmt.Errorf("error getting associations: %w", err) + } + + return nil +} + +// Find finds all document group reviews with the provided query, and assigns +// them to the receiver. +func (d *DocumentGroupReviews) Find(db *gorm.DB, dr DocumentGroupReview) error { + // Validate required fields. + if err := validation.ValidateStruct(&dr.Document, + validation.Field( + &dr.Document.GoogleFileID, + validation.When(dr.Group.EmailAddress == "", + validation.Required.Error( + "at least a Document's GoogleFileID or Group's EmailAddress is required"), + ), + ), + ); err != nil { + return err + } + if err := validation.ValidateStruct(&dr.Group, + validation.Field( + &dr.Group.EmailAddress, + validation.When(dr.Document.GoogleFileID == "", + validation.Required.Error( + "at least a Document's GoogleFileID or Group's EmailAddress is required"), + ), + ), + ); err != nil { + return err + } + + // Get document. + if dr.Document.GoogleFileID != "" { + if err := dr.Document.Get(db); err != nil { + return fmt.Errorf("error getting document: %w", err) + } + dr.DocumentID = dr.Document.ID + } + + // Get group. + if dr.Group.EmailAddress != "" { + if err := dr.Group.Get(db); err != nil { + return fmt.Errorf("error getting group: %w", err) + } + dr.GroupID = dr.Group.ID + } + + return db. + Where(DocumentGroupReview{ + DocumentID: dr.DocumentID, + GroupID: dr.GroupID, + }). + Preload(clause.Associations). + Find(&d). + Error +} + +// Get gets the document group review from database db, and assigns it to the +// receiver. +func (d *DocumentGroupReview) Get(db *gorm.DB) error { + // Validate required fields. + if err := validation.ValidateStruct(&d.Document, + validation.Field(&d.Document.GoogleFileID, validation.Required), + ); err != nil { + return err + } + if err := validation.ValidateStruct(&d.Group, + validation.Field(&d.Group.EmailAddress, validation.Required), + ); err != nil { + return err + } + + if err := d.getAssociations(db); err != nil { + return fmt.Errorf("error getting associations: %w", err) + } + + return db. + Where(DocumentGroupReview{ + DocumentID: d.DocumentID, + GroupID: d.GroupID, + }). + Preload(clause.Associations). + First(&d). + Error +} + +// Update updates the document review in database db. +func (d *DocumentGroupReview) Update(db *gorm.DB) error { + if err := d.getAssociations(db); err != nil { + return fmt.Errorf("error getting associations: %w", err) + } + + return db. + Model(&d). + Omit(clause.Associations). + Updates(*d). + Error +} + +// getAssociations gets associations. +func (d *DocumentGroupReview) getAssociations(db *gorm.DB) error { + // Get document. + if err := d.Document.Get(db); err != nil { + return fmt.Errorf("error getting document: %w", err) + } + d.DocumentID = d.Document.ID + + // Get group. + if err := d.Group.Get(db); err != nil { + return fmt.Errorf("error getting group: %w", err) + } + d.GroupID = d.Group.ID + + return nil +} diff --git a/pkg/models/document_group_review_test.go b/pkg/models/document_group_review_test.go new file mode 100644 index 000000000..b9a702474 --- /dev/null +++ b/pkg/models/document_group_review_test.go @@ -0,0 +1,227 @@ +package models + +import ( + "os" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestDocumentGroupReviewModel(t *testing.T) { + dsn := os.Getenv("HERMES_TEST_POSTGRESQL_DSN") + if dsn == "" { + t.Skip("HERMES_TEST_POSTGRESQL_DSN environment variable isn't set") + } + + t.Run("Create and Get", func(t *testing.T) { + db, tearDownTest := setupTest(t, dsn) + defer tearDownTest(t) + + t.Run("Create a document type", func(t *testing.T) { + _, require := assert.New(t), require.New(t) + dt := DocumentType{ + Name: "DT1", + LongName: "DocumentType1", + } + err := dt.FirstOrCreate(db) + require.NoError(err) + }) + + t.Run("Create a product", func(t *testing.T) { + _, require := assert.New(t), require.New(t) + p := Product{ + Name: "Product1", + Abbreviation: "P1", + } + err := p.FirstOrCreate(db) + require.NoError(err) + }) + + t.Run("Get the review before we create the document", func(t *testing.T) { + _, require := assert.New(t), require.New(t) + dr := DocumentGroupReview{ + Document: Document{ + GoogleFileID: "fileID1", + }, + Group: Group{ + EmailAddress: "team-a@approver.com", + }, + } + err := dr.Get(db) + require.Error(err) + }) + + var d Document + t.Run("Create a document", func(t *testing.T) { + assert, require := assert.New(t), require.New(t) + d = Document{ + GoogleFileID: "fileID1", + ApproverGroups: []*Group{ + { + EmailAddress: "team-a@approver.com", + }, + { + EmailAddress: "team-b@approver.com", + }, + }, + DocumentType: DocumentType{ + Name: "DT1", + }, + Product: Product{ + Name: "Product1", + }, + } + err := d.Create(db) + require.NoError(err) + assert.EqualValues(1, d.ID) + }) + + t.Run("Get the review", func(t *testing.T) { + assert, require := assert.New(t), require.New(t) + dr := DocumentGroupReview{ + Document: Document{ + GoogleFileID: "fileID1", + }, + Group: Group{ + EmailAddress: "team-b@approver.com", + }, + } + err := dr.Get(db) + require.NoError(err) + assert.EqualValues(1, dr.DocumentID) + assert.Equal("fileID1", dr.Document.GoogleFileID) + assert.EqualValues(2, dr.GroupID) + assert.Equal("team-b@approver.com", dr.Group.EmailAddress) + }) + }) + + t.Run("Find", func(t *testing.T) { + db, tearDownTest := setupTest(t, dsn) + defer tearDownTest(t) + + t.Run("Create a document type", func(t *testing.T) { + _, require := assert.New(t), require.New(t) + dt := DocumentType{ + Name: "DT1", + LongName: "DocumentType1", + } + err := dt.FirstOrCreate(db) + require.NoError(err) + }) + + t.Run("Create a product", func(t *testing.T) { + _, require := assert.New(t), require.New(t) + p := Product{ + Name: "Product1", + Abbreviation: "P1", + } + err := p.FirstOrCreate(db) + require.NoError(err) + }) + + var d1, d2, d3 Document + t.Run("Create first document", func(t *testing.T) { + assert, require := assert.New(t), require.New(t) + d1 = Document{ + GoogleFileID: "fileID1", + ApproverGroups: []*Group{ + { + EmailAddress: "team-a@approver.com", + }, + { + EmailAddress: "team-b@approver.com", + }, + }, + DocumentType: DocumentType{ + Name: "DT1", + }, + Product: Product{ + Name: "Product1", + }, + } + err := d1.Create(db) + require.NoError(err) + assert.EqualValues(1, d1.ID) + }) + + t.Run("Create second document", func(t *testing.T) { + assert, require := assert.New(t), require.New(t) + d2 = Document{ + GoogleFileID: "fileID2", + ApproverGroups: []*Group{ + { + EmailAddress: "team-a@approver.com", + }, + }, + DocumentType: DocumentType{ + Name: "DT1", + }, + Product: Product{ + Name: "Product1", + }, + } + err := d2.Create(db) + require.NoError(err) + assert.EqualValues(2, d2.ID) + }) + + t.Run("Create third document", func(t *testing.T) { + assert, require := assert.New(t), require.New(t) + d3 = Document{ + GoogleFileID: "fileID3", + ApproverGroups: []*Group{ + { + EmailAddress: "team-b@approver.com", + }, + }, + DocumentType: DocumentType{ + Name: "DT1", + }, + Product: Product{ + Name: "Product1", + }, + } + err := d3.Create(db) + require.NoError(err) + assert.EqualValues(3, d3.ID) + }) + + t.Run("Find reviews without any search fields", func(t *testing.T) { + _, require := assert.New(t), require.New(t) + var revs DocumentGroupReviews + err := revs.Find(db, DocumentGroupReview{}) + require.Error(err) + }) + + t.Run("Find all reviews for a document", func(t *testing.T) { + assert, require := assert.New(t), require.New(t) + var revs DocumentGroupReviews + err := revs.Find(db, DocumentGroupReview{ + Document: Document{ + GoogleFileID: "fileID1", + }, + }) + require.NoError(err) + require.Len(revs, 2) + assert.Equal("team-a@approver.com", revs[0].Group.EmailAddress) + assert.Equal("team-b@approver.com", revs[1].Group.EmailAddress) + }) + + t.Run("Find all reviews for a group", func(t *testing.T) { + assert, require := assert.New(t), require.New(t) + var revs DocumentGroupReviews + err := revs.Find(db, DocumentGroupReview{ + Group: Group{ + EmailAddress: "team-b@approver.com", + }, + }) + require.NoError(err) + require.Len(revs, 2) + assert.Equal("fileID1", revs[0].Document.GoogleFileID) + assert.Equal("fileID3", revs[1].Document.GoogleFileID) + assert.Equal("team-b@approver.com", revs[0].Group.EmailAddress) + assert.Equal("team-b@approver.com", revs[1].Group.EmailAddress) + }) + }) +}