Merged in feature/batch-status (pull request #215)

Implement and test the batch status feature

* working
This commit is contained in:
Jay Brown
2026-03-12 18:53:42 +00:00
parent 8a4029058b
commit aafe7d5b5f
39 changed files with 4252 additions and 516 deletions
+29 -1
View File
@@ -4,6 +4,8 @@ import (
"context"
"log/slog"
"queryorchestration/internal/database/repository"
"queryorchestration/internal/document/batch/outcome"
documentsync "queryorchestration/internal/document/sync"
"github.com/google/uuid"
@@ -11,30 +13,56 @@ import (
const Name = "docSyncRunner"
// Services holds the dependencies for the docSyncRunner.
//
// Fields:
// - Document: service for syncing documents
// - Outcome: reporter for writing batch pipeline outcomes
type Services struct {
Document *documentsync.Service
Outcome *outcome.Reporter
}
// Runner processes document sync messages from the SQS queue.
type Runner struct {
svc *Services
}
// New creates a new docSyncRunner.
//
// Parameters:
// - svc: the runner's service dependencies
//
// Returns:
// - Runner: the runner instance
func New(svc *Services) Runner {
return Runner{
svc: svc,
}
}
// Body is the SQS message body for docSyncRunner.
type Body struct {
DocumentID uuid.UUID `json:"id" validate:"required,uuid"`
}
// Process handles a single document sync message. Returns true to acknowledge
// the message (success), or false to requeue (infrastructure error).
func (s Runner) Process(ctx context.Context, body Body) bool {
err := s.svc.Document.Sync(ctx, body.DocumentID)
result, err := s.svc.Document.Sync(ctx, body.DocumentID)
if err != nil {
slog.Error("unable to process", "error", err)
return false
}
outcomeName := repository.BatchOutcomeStatusSyncComplete
if result.Skipped {
outcomeName = repository.BatchOutcomeStatusSyncSkipped
}
if updateErr := s.svc.Outcome.Update(ctx, body.DocumentID, outcomeName, nil); updateErr != nil {
slog.Error("failed to update batch outcome",
"document_id", body.DocumentID, "outcome", outcomeName, "error", updateErr)
}
return true
}
+130
View File
@@ -10,6 +10,7 @@ import (
"queryorchestration/internal/client"
"queryorchestration/internal/database/repository"
"queryorchestration/internal/document"
"queryorchestration/internal/document/batch/outcome"
documentsync "queryorchestration/internal/document/sync"
"queryorchestration/internal/server/runner"
"queryorchestration/internal/serviceconfig/objectstore"
@@ -42,6 +43,7 @@ func TestDocSyncRunner(t *testing.T) {
Document: document.New(cfg),
Client: client.New(cfg),
}),
Outcome: outcome.New(cfg.GetDBQueries()),
})
err := cfg.GetDBQueries().CreateClient(t.Context(), &repository.CreateClientParams{
@@ -85,3 +87,131 @@ func TestDocSyncRunner(t *testing.T) {
test.AssertMessageBody(t, cfg, cfg.GetDocumentCleanURL(), regexp.MustCompile(fmt.Sprintf("{\"id\":\"%s\"}", docId)))
}
// setupDocSyncTest creates shared infrastructure and returns the config.
func setupDocSyncTest(t *testing.T) *DocSyncConfig {
t.Helper()
cfg := &DocSyncConfig{}
test.CreateDB(t, cfg)
acfg := test.CreateAWSContainer(t, cfg)
test.SetQueueClient(t, t.Context(), cfg, acfg.ExternalEndpoint)
cfg.DocumentCleanURL = test.CreateQueue(t, cfg, test.DocCleanRunnerName)
return cfg
}
// setupBatchSyncTest creates a client, batch, document, outcome row, and
// returns all IDs needed for a sync runner test.
func setupBatchSyncTest(t *testing.T, cfg *DocSyncConfig, canSync bool) (uuid.UUID, uuid.UUID) {
t.Helper()
ctx := t.Context()
clientID := fmt.Sprintf("sync_%s_%s", fmt.Sprintf("%v", canSync), uuid.New().String()[:8])
// Create client and set sync flag
err := cfg.GetDBQueries().CreateClient(ctx, &repository.CreateClientParams{
Clientid: clientID,
Name: "Test " + clientID,
})
require.NoError(t, err)
err = cfg.GetDBQueries().AddClientCanSync(ctx, &repository.AddClientCanSyncParams{
Clientid: clientID,
Cansync: canSync,
})
require.NoError(t, err)
// Create batch (client must exist first due to FK)
batchID, err := cfg.GetDBQueries().CreateBatchUpload(ctx, &repository.CreateBatchUploadParams{
ClientID: clientID,
OriginalFilename: "test.zip",
TotalDocuments: 1,
})
require.NoError(t, err)
// Create document linked to batch
docID, err := cfg.GetDBQueries().CreateDocument(ctx, &repository.CreateDocumentParams{
Clientid: clientID,
Hash: "hash_" + uuid.New().String()[:8],
BatchID: &batchID,
})
require.NoError(t, err)
part := uint16(1)
filetype := "pdf"
key := objectstore.BucketKey{
CreatedAt: time.Now().UTC(),
ClientID: clientID,
EntityID: uuid.New(),
Location: objectstore.Import,
Part: &part,
FileType: &filetype,
}
err = cfg.GetDBQueries().AddDocumentEntry(ctx, &repository.AddDocumentEntryParams{
Documentid: docID,
Bucket: "bucket",
Key: key.String(),
})
require.NoError(t, err)
// Insert init_complete outcome (the state before sync)
err = cfg.GetDBQueries().InsertBatchDocumentOutcome(ctx, &repository.InsertBatchDocumentOutcomeParams{
BatchID: batchID,
Filename: "test.pdf",
Column3: repository.BatchOutcomeStatusInitComplete,
DocumentID: &docID,
})
require.NoError(t, err)
return batchID, docID
}
// TestDocSyncRunner_ReportsSyncComplete verifies that processing a batch document
// with can_sync=true updates the outcome to sync_complete.
func TestDocSyncRunner_ReportsSyncComplete(t *testing.T) {
cfg := setupDocSyncTest(t)
ctx := t.Context()
batchID, docID := setupBatchSyncTest(t, cfg, true)
syncRunner := docsyncrunner.New(&docsyncrunner.Services{
Document: documentsync.New(cfg, &documentsync.Services{
Document: document.New(cfg),
Client: client.New(cfg),
}),
Outcome: outcome.New(cfg.GetDBQueries()),
})
assert.True(t, syncRunner.Process(ctx, docsyncrunner.Body{DocumentID: docID}))
// Verify outcome updated to sync_complete
rows, err := cfg.GetDBQueries().ListBatchDocumentOutcomes(ctx, batchID)
require.NoError(t, err)
require.Len(t, rows, 1)
assert.Equal(t, repository.BatchOutcomeStatusSyncComplete, rows[0].Outcome)
}
// TestDocSyncRunner_ReportsSyncSkipped verifies that processing a batch document
// with can_sync=false updates the outcome to sync_skipped.
func TestDocSyncRunner_ReportsSyncSkipped(t *testing.T) {
cfg := setupDocSyncTest(t)
ctx := t.Context()
batchID, docID := setupBatchSyncTest(t, cfg, false)
syncRunner := docsyncrunner.New(&docsyncrunner.Services{
Document: documentsync.New(cfg, &documentsync.Services{
Document: document.New(cfg),
Client: client.New(cfg),
}),
Outcome: outcome.New(cfg.GetDBQueries()),
})
assert.True(t, syncRunner.Process(ctx, docsyncrunner.Body{DocumentID: docID}))
// Verify outcome updated to sync_skipped
rows, err := cfg.GetDBQueries().ListBatchDocumentOutcomes(ctx, batchID)
require.NoError(t, err)
require.Len(t, rows, 1)
assert.Equal(t, repository.BatchOutcomeStatusSyncSkipped, rows[0].Outcome)
}