package diagnostics import ( "context" "errors" "os" "testing" "time" "data-terminal-cloud/internal/audit" "data-terminal-cloud/internal/concurrency" "data-terminal-cloud/internal/database" "data-terminal-cloud/internal/idempotency" "data-terminal-cloud/internal/identity" "github.com/google/uuid" ) func TestIntegrationIssueLifecycleIsTenantScopedIdempotentAndVersioned(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, "integration-issue-test") if err != nil { t.Fatal(err) } defer pool.Close() service, err := NewService(pool, idempotency.NewExecutor(pool), audit.NewRepository()) if err != nil { t.Fatal(err) } admin := identity.Principal{Issuer: "http://localhost:58180/realms/voice-local", Subject: "00000000-0000-0000-0000-000000000101"} tenantPublicID := "10000000-0000-0000-0000-000000000010" devicePublicID := "10000000-0000-0000-0000-000000000511" key := "integration-issue-" + uuid.NewString() requestPrefix := "integration-issue-test-" + uuid.NewString() baselineSummary, err := service.SummarizeIntegrationIssues(ctx, admin, tenantPublicID) if err != nil { t.Fatal(err) } var issueID string defer func() { if issueID != "" { _, _ = pool.Exec(context.Background(), `DELETE FROM integration_issue WHERE public_id=$1::uuid`, issueID) } _, _ = pool.Exec(context.Background(), `DELETE FROM audit_event WHERE request_id LIKE $1`, requestPrefix+"%") _, _ = pool.Exec(context.Background(), `DELETE FROM idempotency_record WHERE idempotency_key=$1`, key) }() input := CreateIntegrationIssueInput{ Phenomenon: "设备入房后供应商任务未在预期时间进入活动状态", Severity: "HIGH", DeviceID: devicePublicID, OccurredAt: time.Now().UTC().Add(-time.Minute), RequestID: requestPrefix + "-source", ResponsibleParty: "Agent研发组", } created, firstCreated, err := service.CreateIntegrationIssue(ctx, admin, tenantPublicID, key, requestPrefix+"-create", input) if err != nil || !firstCreated || created.Status != "OPEN" || created.Severity != "HIGH" || created.Version != 1 || created.DeviceID != devicePublicID { t.Fatalf("created=%+v first=%t err=%v", created, firstCreated, err) } issueID = created.ID replayed, replayCreated, err := service.CreateIntegrationIssue(ctx, admin, tenantPublicID, key, requestPrefix+"-replay", input) if err != nil || replayCreated || replayed.ID != created.ID { t.Fatalf("replayed=%+v created=%t err=%v", replayed, replayCreated, err) } items, err := service.ListIntegrationIssues(ctx, admin, tenantPublicID, "OPEN") if err != nil || len(items) == 0 || items[0].ID != created.ID { t.Fatalf("items=%+v err=%v", items, err) } blockingSummary, err := service.SummarizeIntegrationIssues(ctx, admin, tenantPublicID) if err != nil || blockingSummary.ReleaseBlocking != baselineSummary.ReleaseBlocking+1 || blockingSummary.ReleaseGateSatisfied { t.Fatalf("blocking summary=%+v baseline=%+v err=%v", blockingSummary, baselineSummary, err) } resolvedStatus, resolution := "RESOLVED", "修正供应商任务启动回调的路由配置" rootCause, fixedVersion := "回调路由错误地使用了旧供应商事件类型", "0.49.0-rc.1+build.36" if _, err = service.UpdateIntegrationIssue(ctx, admin, tenantPublicID, created.ID, requestPrefix+"-invalid-resolve", 1, UpdateIntegrationIssueInput{Status: &resolvedStatus}); !errors.Is(err, ErrInvalid) { t.Fatalf("missing resolution error=%v", err) } resolved, err := service.UpdateIntegrationIssue(ctx, admin, tenantPublicID, created.ID, requestPrefix+"-resolve", 1, UpdateIntegrationIssueInput{Status: &resolvedStatus, ResolutionResult: &resolution, RootCause: &rootCause, FixedVersion: &fixedVersion}) if err != nil || resolved.Status != "RESOLVED" || resolved.Version != 2 || resolved.ResolutionResult != resolution || resolved.RootCause != rootCause || resolved.FixedVersion != fixedVersion { t.Fatalf("resolved=%+v err=%v", resolved, err) } if _, err = service.UpdateIntegrationIssue(ctx, admin, tenantPublicID, created.ID, requestPrefix+"-stale", 1, UpdateIntegrationIssueInput{ResponsibleParty: &resolution}); !errors.Is(err, concurrency.ErrVersionConflict) { t.Fatalf("stale version error=%v", err) } verifiedStatus, conclusion := "VERIFIED", "连续三次模拟联调均进入ACTIVE且没有遗留任务" evidence := "请求关联号 integration-regression-36;诊断包 artifact-36;重复3次" if _, err = service.UpdateIntegrationIssue(ctx, admin, tenantPublicID, created.ID, requestPrefix+"-invalid-verify", 2, UpdateIntegrationIssueInput{Status: &verifiedStatus, RegressionConclusion: &conclusion}); !errors.Is(err, ErrInvalid) { t.Fatalf("missing regression evidence error=%v", err) } verified, err := service.UpdateIntegrationIssue(ctx, admin, tenantPublicID, created.ID, requestPrefix+"-verify", 2, UpdateIntegrationIssueInput{Status: &verifiedStatus, RegressionConclusion: &conclusion, RegressionEvidence: &evidence}) if err != nil || verified.Status != "VERIFIED" || verified.Version != 3 || verified.RegressionConclusion != conclusion || verified.RegressionEvidence != evidence { t.Fatalf("verified=%+v err=%v", verified, err) } closedStatus := "CLOSED" closed, err := service.UpdateIntegrationIssue(ctx, admin, tenantPublicID, created.ID, requestPrefix+"-close", 3, UpdateIntegrationIssueInput{Status: &closedStatus}) if err != nil || closed.Version != 4 || closed.Status != "CLOSED" { t.Fatalf("closed=%+v err=%v", closed, err) } closedSummary, err := service.SummarizeIntegrationIssues(ctx, admin, tenantPublicID) if err != nil || closedSummary.ReleaseBlocking != baselineSummary.ReleaseBlocking || closedSummary.ReleaseGateSatisfied != baselineSummary.ReleaseGateSatisfied { t.Fatalf("closed summary=%+v baseline=%+v err=%v", closedSummary, baselineSummary, err) } if _, err = service.ListIntegrationIssues(ctx, admin, "20000000-0000-0000-0000-000000000010", ""); !errors.Is(err, ErrAccessDenied) { t.Fatalf("cross tenant error=%v", err) } }