package document_test import ( "context" "testing" "queryorchestration/internal/database/repository" "queryorchestration/internal/document" "queryorchestration/internal/fieldextraction" "queryorchestration/internal/serviceconfig" "queryorchestration/internal/test" "github.com/stretchr/testify/require" "github.com/google/uuid" "github.com/pashagolub/pgxmock/v3" "github.com/stretchr/testify/assert" ) type TestConfig struct { serviceconfig.BaseConfig } func TestGetSummary(t *testing.T) { ctx := t.Context() pool, err := pgxmock.NewPool() require.NoError(t, err) cfg := &serviceconfig.BaseConfig{} cfg.DBPool = pool cfg.DBQueries = repository.New(pool) svc := document.New(cfg) doc := document.DocumentSummary{ ID: uuid.New(), ClientID: "example", Hash: "example_hash", } pool.ExpectQuery("name: GetDocumentSummary :one").WithArgs(doc.ID). WillReturnRows( pgxmock.NewRows([]string{"id", "clientId", "hash"}). AddRow(doc.ID, doc.ClientID, doc.Hash), ) adoc, err := svc.GetSummary(ctx, doc.ID) require.NoError(t, err) assert.Equal(t, &doc, adoc) } func TestGetExternal(t *testing.T) { ctx := t.Context() pool, err := pgxmock.NewPool() require.NoError(t, err) cfg := &serviceconfig.BaseConfig{} cfg.DBPool = pool cfg.DBQueries = repository.New(pool) svc := document.New(cfg) // Note: Query functionality (Fields) has been removed. See remove_query_plan.md for details. doc := document.DocumentExternal{ Id: uuid.New(), ClientID: "externalID", Hash: "example", } pool.ExpectQuery("name: GetDocumentExternal :one").WithArgs(doc.Id). WillReturnRows( pgxmock.NewRows([]string{"id", "clientId", "hash"}). AddRow(doc.Id, "externalID", "example"), ) adoc, err := svc.GetExternal(ctx, doc.Id) require.NoError(t, err) assert.Equal(t, &doc, adoc) } func TestGetEnriched(t *testing.T) { ctx := t.Context() cfg := &TestConfig{} test.CreateDB(t, cfg) svc := document.New(cfg) // Create test client clientID := "test-client-enriched" err := cfg.GetDBQueries().CreateClient(ctx, &repository.CreateClientParams{ Clientid: clientID, Name: "Test Client Enriched", }) require.NoError(t, err) t.Run("get enriched document without text record", func(t *testing.T) { filename := "test-enriched.pdf" originalPath := "/documents/test-enriched.pdf" documentID, err := cfg.GetDBQueries().CreateDocument(ctx, &repository.CreateDocumentParams{ Clientid: clientID, Hash: "enrichedhash1", Filename: &filename, Originalpath: &originalPath, }) require.NoError(t, err) // Get enriched document without text record result, err := svc.GetEnriched(ctx, documentID, false) require.NoError(t, err) assert.NotNil(t, result) assert.Equal(t, documentID, result.ID) assert.Equal(t, clientID, result.ClientID) assert.Equal(t, "enrichedhash1", result.Hash) assert.False(t, result.HasTextRecord) assert.Equal(t, filename, *result.Filename) assert.Equal(t, originalPath, *result.OriginalPath) assert.Nil(t, result.TextRecord) assert.Empty(t, result.Labels) }) t.Run("get enriched document with text record requested but not present", func(t *testing.T) { filename := "test-enriched-no-text.pdf" documentID, err := cfg.GetDBQueries().CreateDocument(ctx, &repository.CreateDocumentParams{ Clientid: clientID, Hash: "enrichedhash2", Filename: &filename, }) require.NoError(t, err) // Get enriched document with text record requested result, err := svc.GetEnriched(ctx, documentID, true) require.NoError(t, err) assert.NotNil(t, result) assert.Equal(t, documentID, result.ID) assert.False(t, result.HasTextRecord) assert.Nil(t, result.TextRecord) // No text record exists }) t.Run("get enriched document with text record", func(t *testing.T) { filename := "test-enriched-with-text.pdf" documentID, err := cfg.GetDBQueries().CreateDocument(ctx, &repository.CreateDocumentParams{ Clientid: clientID, Hash: "enrichedhash3", Filename: &filename, }) require.NoError(t, err) // Create field extraction for this document fieldSvc := fieldextraction.New(cfg) contractTitle := "Test Contract" input := &fieldextraction.CreateFieldExtractionInput{ DocumentID: documentID, SingleFields: &repository.AddFieldExtractionParams{ Documentid: documentID, Filename: &filename, Contracttitle: &contractTitle, Createdby: "user123", }, ArrayFields: []*repository.AddFieldExtractionArrayFieldParams{}, } _, err = fieldSvc.CreateFieldExtraction(ctx, input) require.NoError(t, err) // Get enriched document with text record result, err := svc.GetEnriched(ctx, documentID, true) require.NoError(t, err) assert.NotNil(t, result) assert.Equal(t, documentID, result.ID) assert.True(t, result.HasTextRecord) assert.NotNil(t, result.TextRecord) assert.Equal(t, documentID, result.TextRecord.DocumentID) assert.Equal(t, int32(1), result.TextRecord.Version) assert.Equal(t, "user123", result.TextRecord.CreatedBy) }) t.Run("get enriched document with text record but not requesting it", func(t *testing.T) { filename := "test-enriched-skip-text.pdf" documentID, err := cfg.GetDBQueries().CreateDocument(ctx, &repository.CreateDocumentParams{ Clientid: clientID, Hash: "enrichedhash4", Filename: &filename, }) require.NoError(t, err) // Create field extraction fieldSvc := fieldextraction.New(cfg) contractTitle := "Test Contract Skip" input := &fieldextraction.CreateFieldExtractionInput{ DocumentID: documentID, SingleFields: &repository.AddFieldExtractionParams{ Documentid: documentID, Filename: &filename, Contracttitle: &contractTitle, Createdby: "user456", }, ArrayFields: []*repository.AddFieldExtractionArrayFieldParams{}, } _, err = fieldSvc.CreateFieldExtraction(ctx, input) require.NoError(t, err) // Get enriched document without requesting text record result, err := svc.GetEnriched(ctx, documentID, false) require.NoError(t, err) assert.NotNil(t, result) assert.True(t, result.HasTextRecord) // Has text record assert.Nil(t, result.TextRecord) // But not fetched }) t.Run("return error for non-existent document", func(t *testing.T) { nonExistentID := uuid.New() _, err := svc.GetEnriched(ctx, nonExistentID, false) require.Error(t, err) }) } // TestGetEnriched_CustomSchemaFields covers Milestone 2.3a: the // DocumentEnriched struct surfaces CustomSchemaID and HasCustomMetadata // populated from the extended GetDocumentEnriched query in a single // round-trip. Three fixture shapes exercise every branch of the two new // fields. func TestGetEnriched_CustomSchemaFields(t *testing.T) { ctx := t.Context() cfg := &TestConfig{} test.CreateDB(t, cfg) svc := document.New(cfg) clientID := "custom-schema-enriched" // testcontainer pg instances are reused across runs; wipe the client's // rows before re-seeding so the test is idempotent. resetClientForEnrichedTest(t, ctx, cfg, clientID) require.NoError(t, cfg.GetDBQueries().CreateClient(ctx, &repository.CreateClientParams{ Clientid: clientID, Name: "Custom Schema Enriched", })) // Seed one active schema for this client via direct SQL (avoids a // hard dependency on the customschema package in the document tests). var schemaID uuid.UUID require.NoError(t, cfg.GetDBPool().QueryRow(ctx, ` INSERT INTO client_metadata_schemas (client_id, name, schema_def, version, created_by) VALUES ($1, $2, $3::jsonb, 1, 'tester') RETURNING id `, clientID, "enriched-fields", `{"type":"object","additionalProperties":false}`).Scan(&schemaID)) t.Run("document with no schema bound", func(t *testing.T) { docID, err := cfg.GetDBQueries().CreateDocument(ctx, &repository.CreateDocumentParams{ Clientid: clientID, Hash: "custom-no-schema", }) require.NoError(t, err) result, err := svc.GetEnriched(ctx, docID, false) require.NoError(t, err) assert.Nil(t, result.CustomSchemaID, "unbound document must have CustomSchemaID = nil") assert.False(t, result.HasCustomMetadata, "unbound document must have HasCustomMetadata = false") }) t.Run("document with schema bound but no metadata", func(t *testing.T) { docID, err := cfg.GetDBQueries().CreateDocument(ctx, &repository.CreateDocumentParams{ Clientid: clientID, Hash: "custom-schema-no-metadata", }) require.NoError(t, err) _, err = cfg.GetDBPool().Exec(ctx, `UPDATE documents SET custom_schema_id = $1 WHERE id = $2`, schemaID, docID) require.NoError(t, err) result, err := svc.GetEnriched(ctx, docID, false) require.NoError(t, err) require.NotNil(t, result.CustomSchemaID, "bound document must carry CustomSchemaID") assert.Equal(t, schemaID, *result.CustomSchemaID) assert.False(t, result.HasCustomMetadata, "bound document with no metadata must be false") }) t.Run("document with schema and at least one metadata row", func(t *testing.T) { docID, err := cfg.GetDBQueries().CreateDocument(ctx, &repository.CreateDocumentParams{ Clientid: clientID, Hash: "custom-schema-with-metadata", }) require.NoError(t, err) _, err = cfg.GetDBPool().Exec(ctx, `UPDATE documents SET custom_schema_id = $1 WHERE id = $2`, schemaID, docID) require.NoError(t, err) _, err = cfg.GetDBPool().Exec(ctx, ` INSERT INTO document_custom_metadata (document_id, metadata, version, created_by) VALUES ($1, '{"k":"v"}'::jsonb, 1, 'tester') `, docID) require.NoError(t, err) result, err := svc.GetEnriched(ctx, docID, false) require.NoError(t, err) require.NotNil(t, result.CustomSchemaID) assert.Equal(t, schemaID, *result.CustomSchemaID) assert.True(t, result.HasCustomMetadata, "document with metadata row must be true") }) } // resetClientForEnrichedTest wipes every row owned by clientID in the order // required by the FK graph so the test starts clean even when the // testcontainer Postgres volume is reused across runs. Mirrors the pattern // from internal/database/repository/milestone2_migrations_test.go. func resetClientForEnrichedTest(t testing.TB, ctx context.Context, cfg serviceconfig.ConfigProvider, clientID string) { t.Helper() pool := cfg.GetDBPool() stmts := []string{ `DELETE FROM document_custom_metadata WHERE document_id IN (SELECT id FROM documents WHERE clientId = $1)`, `UPDATE documents SET custom_schema_id = NULL WHERE clientId = $1`, `DELETE FROM client_metadata_schemas WHERE client_id = $1`, `DELETE FROM documentEntries WHERE documentId IN (SELECT id FROM documents WHERE clientId = $1)`, `DELETE FROM documents WHERE clientId = $1`, `DELETE FROM folders WHERE clientId = $1`, `DELETE FROM clientCanSync WHERE clientId = $1`, `DELETE FROM clients WHERE clientId = $1`, } for _, stmt := range stmts { _, err := pool.Exec(ctx, stmt, clientID) require.NoErrorf(t, err, "reset stmt failed: %s", stmt) } }