Files
Jay Brown 17fc813823 Merged in feature/mutable-metadata1 (pull request #221)
M1, M2 and M3 complete

* M1, M2 and M3 complete

* review changes

* docs

* docs
2026-04-16 23:11:26 +00:00

321 lines
11 KiB
Go

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