package testsuite import ( "context" "testing" "forge.cadoles.com/arcad/edge/pkg/storage" "forge.cadoles.com/arcad/edge/pkg/storage/filter" "github.com/pkg/errors" ) type documentStoreOpsTestCase struct { Name string Run func(ctx context.Context, store storage.DocumentStore) error } var documentStoreOpsTestCases = []documentStoreOpsTestCase{ { Name: "Basic query", Run: func(ctx context.Context, store storage.DocumentStore) error { collection := "simple_select" docs := []storage.Document{ { "attr1": "Foo", }, { "attr1": "Bar", }, } for _, d := range docs { if _, err := store.Upsert(ctx, collection, d); err != nil { return errors.WithStack(err) } } filter := filter.New( filter.NewEqOperator(map[string]interface{}{ "attr1": "Foo", }), ) results, err := store.Query(ctx, collection, filter) if err != nil { return errors.WithStack(err) } if e, g := 1, len(results); e != g { return errors.Errorf("len(results): expected '%v', got '%v'", e, g) } if e, g := "Foo", results[0]["attr1"]; e != g { return errors.Errorf("results[0][\"Attr1\"]: expected '%v', got '%v'", e, g) } return nil }, }, { Name: "Query on _id", Run: func(ctx context.Context, store storage.DocumentStore) error { collection := "query_on_id" doc := storage.Document{ "attr1": "Foo", } upsertedDoc, err := store.Upsert(ctx, collection, doc) if err != nil { return errors.WithStack(err) } docID, ok := upsertedDoc.ID() if !ok { return errors.Errorf("") } filter := filter.New( filter.NewEqOperator(map[string]interface{}{ "_id": docID, }), ) results, err := store.Query(ctx, collection, filter) if err != nil { return errors.WithStack(err) } if e, g := 1, len(results); e != g { return errors.Errorf("len(results): expected '%v', got '%v'", e, g) } return nil }, }, { Name: "Query with 'IN' operator", Run: func(ctx context.Context, store storage.DocumentStore) error { docs := []storage.Document{ { "counter": 1, "tags": []string{"foo", "bar"}, }, { "counter": 1, "tags": []string{"nope"}, }, } collection := "in_operator" for _, doc := range docs { if _, err := store.Upsert(ctx, collection, doc); err != nil { return errors.WithStack(err) } } filter := filter.New( filter.NewAndOperator( filter.NewEqOperator(map[string]any{ "counter": 1, }), filter.NewInOperator(map[string]any{ "tags": "foo", }), ), ) results, err := store.Query(ctx, collection, filter) if err != nil { return errors.WithStack(err) } if e, g := 1, len(results); e != g { return errors.Errorf("len(results): expected '%v', got '%v'", e, g) } return nil }, }, { Name: "Double upsert", Run: func(ctx context.Context, store storage.DocumentStore) error { collection := "double_upsert" oriDoc := storage.Document{ "attr1": "Foo", } // Upsert document for the first time upsertedDoc, err := store.Upsert(ctx, collection, oriDoc) if err != nil { return errors.WithStack(err) } id, exists := upsertedDoc.ID() if !exists { return errors.New("id, exists := upsertedDoc.ID(): 'exists' should be true") } if id == storage.DocumentID("") { return errors.New("id, exists := upsertedDoc.ID(): 'id' should not be an empty string") } createdAt, exists := upsertedDoc.CreatedAt() if !exists { return errors.New("createdAt, exists := upsertedDoc.CreatedAt(): 'exists' should be true") } if createdAt.IsZero() { return errors.New("createdAt, exists := upsertedDoc.CreatedAt(): 'createdAt' should not be zero time") } updatedAt, exists := upsertedDoc.UpdatedAt() if !exists { return errors.New("updatedAt, exists := upsertedDoc.UpdatedAt(): 'exists' should be true") } if updatedAt.IsZero() { return errors.New("updatedAt, exists := upsertedDoc.UpdatedAt(): 'updatedAt' should not be zero time") } if e, g := oriDoc["attr1"], upsertedDoc["attr1"]; e != g { return errors.Errorf("upsertedDoc[\"attr1\"]: expected '%v', got '%v'", e, g) } upsertedDocRevision, _ := upsertedDoc.Revision() if e, g := 0, upsertedDocRevision; e != g { return errors.Errorf("upsertedDoc.Revision(): expected '%v', got '%v'", e, g) } // Check that document does not have unexpected properties if e, g := 5, len(upsertedDoc); e != g { return errors.Errorf("len(upsertedDoc): expected '%v', got '%v'", e, g) } // Upsert document for the second time upsertedDoc2, err := store.Upsert(ctx, collection, upsertedDoc) if err != nil { return errors.WithStack(err) } prevID, _ := upsertedDoc.ID() newID, _ := upsertedDoc2.ID() if e, g := prevID, newID; e != g { return errors.Errorf("newID: expected '%v', got '%v'", e, g) } createdAt1, _ := upsertedDoc.CreatedAt() createdAt2, _ := upsertedDoc2.CreatedAt() if e, g := createdAt1, createdAt2; e != g { return errors.Errorf("upsertedDoc2.CreatedAt(): expected '%v', got '%v'", e, g) } updatedAt1, _ := upsertedDoc.UpdatedAt() updatedAt2, _ := upsertedDoc2.UpdatedAt() if e, g := updatedAt1, updatedAt2; e == g { return errors.New("upsertedDoc2.UpdatedAt() should have been different than upsertedDoc.UpdatedAt()") } upsertedDoc2Revision, _ := upsertedDoc2.Revision() if e, g := 1, upsertedDoc2Revision; e != g { return errors.Errorf("upsertedDoc.Revision(): expected '%v', got '%v'", e, g) } // Verify that there is no additional created document in the collection results, err := store.Query(ctx, collection, nil) if err != nil { return errors.WithStack(err) } if e, g := 1, len(results); e != g { return errors.Errorf("len(results): expected '%v', got '%v'", e, g) } firstResultRevision, _ := results[0].Revision() if e, g := 1, firstResultRevision; e != g { return errors.Errorf("results[0].Revision(): expected '%v', got '%v'", e, g) } return nil }, }, { Name: "Query order by document field", Run: func(ctx context.Context, store storage.DocumentStore) error { docs := []storage.Document{ { "sortedField": 0, "name": "Item 1", }, { "sortedField": 1, "name": "Item 2", }, { "sortedField": 2, "name": "Item 3", }, } collection := "ordered_query_by_document_field" for _, doc := range docs { if _, err := store.Upsert(ctx, collection, doc); err != nil { return errors.WithStack(err) } } results, err := store.Query( ctx, collection, nil, storage.WithOrderBy("sortedField"), storage.WithOrderDirection(storage.OrderDirectionAsc), ) if err != nil { return errors.WithStack(err) } if e, g := 3, len(results); e != g { return errors.Errorf("len(results): expected '%v', got '%v'", e, g) } if e, g := docs[0]["name"], results[0]["name"]; e != g { return errors.Errorf("results[0][\"name\"]: expected '%v', got '%v'", e, g) } if e, g := docs[2]["name"], results[2]["name"]; e != g { return errors.Errorf("results[2][\"name\"]: expected '%v', got '%v'", e, g) } results, err = store.Query( ctx, collection, nil, storage.WithOrderBy("sortedField"), storage.WithOrderDirection(storage.OrderDirectionDesc), ) if err != nil { return errors.WithStack(err) } if e, g := 3, len(results); e != g { return errors.Errorf("len(results): expected '%v', got '%v'", e, g) } if e, g := docs[2]["name"], results[0]["name"]; e != g { return errors.Errorf("results[0][\"name\"]: expected '%v', got '%v'", e, g) } if e, g := docs[0]["name"], results[2]["name"]; e != g { return errors.Errorf("results[2][\"name\"]: expected '%v', got '%v'", e, g) } return nil }, }, { Name: "Query order by special attr", Run: func(ctx context.Context, store storage.DocumentStore) error { docs := []storage.Document{ { "name": "Item 1", }, { "name": "Item 2", }, { "name": "Item 3", }, } collection := "ordered_query_by_special_attr" for _, doc := range docs { if _, err := store.Upsert(ctx, collection, doc); err != nil { return errors.WithStack(err) } } results, err := store.Query( ctx, collection, nil, storage.WithOrderBy(storage.DocumentAttrCreatedAt), storage.WithOrderDirection(storage.OrderDirectionAsc), ) if err != nil { return errors.WithStack(err) } if e, g := 3, len(results); e != g { return errors.Errorf("len(results): expected '%v', got '%v'", e, g) } if e, g := docs[0]["name"], results[0]["name"]; e != g { return errors.Errorf("results[0][\"name\"]: expected '%v', got '%v'", e, g) } if e, g := docs[2]["name"], results[2]["name"]; e != g { return errors.Errorf("results[2][\"name\"]: expected '%v', got '%v'", e, g) } results, err = store.Query( ctx, collection, nil, storage.WithOrderBy(storage.DocumentAttrCreatedAt), storage.WithOrderDirection(storage.OrderDirectionDesc), ) if err != nil { return errors.WithStack(err) } if e, g := 3, len(results); e != g { return errors.Errorf("len(results): expected '%v', got '%v'", e, g) } if e, g := docs[2]["name"], results[0]["name"]; e != g { return errors.Errorf("results[0][\"name\"]: expected '%v', got '%v'", e, g) } if e, g := docs[0]["name"], results[2]["name"]; e != g { return errors.Errorf("results[2][\"name\"]: expected '%v', got '%v'", e, g) } return nil }, }, { Name: "Query limit and offset", Run: func(ctx context.Context, store storage.DocumentStore) error { docs := []storage.Document{ {"name": "Item 1"}, {"name": "Item 2"}, {"name": "Item 3"}, {"name": "Item 4"}, } collection := "query_limit_and_offset" for _, doc := range docs { if _, err := store.Upsert(ctx, collection, doc); err != nil { return errors.WithStack(err) } } results, err := store.Query( ctx, collection, nil, storage.WithLimit(2), ) if err != nil { return errors.WithStack(err) } if e, g := 2, len(results); e != g { return errors.Errorf("len(results): expected '%v', got '%v'", e, g) } if e, g := docs[0]["name"], results[0]["name"]; e != g { return errors.Errorf("results[0][\"name\"]: expected '%v', got '%v'", e, g) } if e, g := docs[1]["name"], results[1]["name"]; e != g { return errors.Errorf("results[1][\"name\"]: expected '%v', got '%v'", e, g) } results, err = store.Query( ctx, collection, nil, storage.WithOffset(2), ) if err != nil { return errors.WithStack(err) } if e, g := 2, len(results); e != g { return errors.Errorf("len(results): expected '%v', got '%v'", e, g) } if e, g := docs[2]["name"], results[0]["name"]; e != g { return errors.Errorf("results[0][\"name\"]: expected '%v', got '%v'", e, g) } if e, g := docs[3]["name"], results[1]["name"]; e != g { return errors.Errorf("results[1][\"name\"]: expected '%v', got '%v'", e, g) } return nil }, }, } func testDocumentStoreOps(ctx context.Context, t *testing.T, store storage.DocumentStore) { for _, tc := range documentStoreOpsTestCases { func(tc documentStoreOpsTestCase) { t.Run(tc.Name, func(t *testing.T) { if err := tc.Run(ctx, store); err != nil { t.Errorf("%+v", errors.WithStack(err)) } }) }(tc) } }