package privacy import ( "context" "encoding/json" "os" "strings" "testing" "data-terminal-cloud/internal/database" "data-terminal-cloud/internal/outbox" "github.com/jackc/pgx/v5/pgxpool" ) const ( privacyAccessRequestID = "90000000-0000-0000-0000-000000000701" privacyAccessRequestPublicID = "90000000-0000-0000-0000-000000000711" privacyWrongRequestID = "90000000-0000-0000-0000-000000000702" ) func TestAccessProcessorCompletesScopedInventoryIdempotently(t *testing.T) { databaseURL := os.Getenv("TEST_DATABASE_URL") if databaseURL == "" { t.Skip("TEST_DATABASE_URL is not set") } ctx := context.Background() pool, err := database.Open(ctx, databaseURL, "privacy-access-test") if err != nil { t.Fatal(err) } defer pool.Close() cleanupPrivacyAccessFixture(t, ctx, pool) defer cleanupPrivacyAccessFixture(t, context.Background(), pool) if _, err := pool.Exec(ctx, `INSERT INTO privacy_request ( id,public_id,tenant_id,subject_id,request_type,scope,requested_by,idempotency_key,request_digest ) VALUES ( $1::uuid,$2::uuid,'10000000-0000-0000-0000-000000000001','10000000-0000-0000-0000-000000000401', 'ACCESS','{"categories":["profile","conversations","unknown",7]}'::jsonb, '10000000-0000-0000-0000-000000000101','privacy-access-integration',repeat('7',64) )`, privacyAccessRequestID, privacyAccessRequestPublicID); err != nil { t.Fatal(err) } if _, err := pool.Exec(ctx, `INSERT INTO privacy_subtask ( public_id,privacy_request_id,subsystem,target_type,target_ref ) VALUES ( '90000000-0000-0000-0000-000000000721',$1::uuid,'postgresql','subject_data_inventory','subject' )`, privacyAccessRequestID); err != nil { t.Fatal(err) } processor, err := NewAccessProcessor(pool) if err != nil { t.Fatal(err) } if err := processor.Handle(ctx, outbox.Event{AggregateID: privacyAccessRequestID}); err != nil { t.Fatal(err) } var status, taskStatus string var attempts int var resultBody []byte if err := pool.QueryRow(ctx, `SELECT pr.status,pr.result_summary,ps.status,ps.attempts FROM privacy_request pr JOIN privacy_subtask ps ON ps.privacy_request_id=pr.id WHERE pr.id=$1::uuid`, privacyAccessRequestID).Scan(&status, &resultBody, &taskStatus, &attempts); err != nil { t.Fatal(err) } var result struct { InventoryCounts map[string]int64 `json:"inventory_counts"` ContentIncluded bool `json:"content_included"` } if err := json.Unmarshal(resultBody, &result); err != nil { t.Fatal(err) } if status != "COMPLETED" || taskStatus != "COMPLETED" || attempts != 1 || result.ContentIncluded || len(result.InventoryCounts) != 2 || result.InventoryCounts["profile"] != 1 || result.InventoryCounts["conversations"] != 0 { t.Fatalf("status=%s task=%s attempts=%d result=%#v", status, taskStatus, attempts, result) } if err := processor.Handle(ctx, outbox.Event{AggregateID: privacyAccessRequestID}); err != nil { t.Fatal(err) } var audits int if err := pool.QueryRow(ctx, `SELECT count(*) FROM audit_event WHERE action='privacy.access.complete' AND resource_id=$1::uuid`, privacyAccessRequestPublicID).Scan(&audits); err != nil { t.Fatal(err) } if audits != 1 { t.Fatalf("privacy access audits=%d", audits) } if err := processor.Handle(ctx, outbox.Event{AggregateID: "90000000-0000-0000-0000-000000000799"}); err != nil { t.Fatalf("missing request error=%v", err) } if _, err := pool.Exec(ctx, `INSERT INTO privacy_request ( id,public_id,tenant_id,subject_id,request_type,scope,requested_by,idempotency_key,request_digest ) VALUES ( $1::uuid,'90000000-0000-0000-0000-000000000712','10000000-0000-0000-0000-000000000001', '10000000-0000-0000-0000-000000000401','CORRECTION','{}'::jsonb, '10000000-0000-0000-0000-000000000101','privacy-access-wrong-type',repeat('8',64) )`, privacyWrongRequestID); err != nil { t.Fatal(err) } if err := processor.Handle(ctx, outbox.Event{AggregateID: privacyWrongRequestID}); err == nil || !strings.Contains(err.Error(), "CORRECTION") { t.Fatalf("wrong request type error=%v", err) } } func cleanupPrivacyAccessFixture(t *testing.T, ctx context.Context, pool *pgxpool.Pool) { t.Helper() statements := []string{ `DELETE FROM audit_event WHERE action='privacy.access.complete' AND resource_id IN ('90000000-0000-0000-0000-000000000711'::uuid,'90000000-0000-0000-0000-000000000712'::uuid)`, `DELETE FROM privacy_request WHERE id IN ('90000000-0000-0000-0000-000000000701'::uuid,'90000000-0000-0000-0000-000000000702'::uuid)`, } for _, statement := range statements { if _, err := pool.Exec(ctx, statement); err != nil { t.Fatalf("cleanup privacy access fixture: %v", err) } } }