2026-08-26 06:41:07 +08:00
|
|
|
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)
|
|
|
|
|
}
|
2026-08-29 21:56:51 +08:00
|
|
|
admin := identity.Principal{Issuer: "http://localhost:58180/realms/voice-local", Subject: "00000000-0000-0000-0000-000000000104"}
|
2026-08-26 06:41:07 +08:00
|
|
|
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)
|
|
|
|
|
}
|
|
|
|
|
}
|