09b2c1dce5
build / build (push) Successful in 1m17s
修正筛选入口位置:五项条件在表头就地选择,保持完整集筛选与无感续载。 统一固定规则:未覆盖一律待确认,原问答/人工模型证据不改写,已覆盖仍保留点踩优先。 验证:行为红绿、前端/Go全套及隔离PostgreSQL全mirror竞态通过;完整本地门禁与原生备份真实恢复通过。
157 lines
7.6 KiB
Go
157 lines
7.6 KiB
Go
package qaopsmirror
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"errors"
|
|
"github.com/jackc/pgx/v5/pgconn"
|
|
"knowledge-governance/internal/dobotaiops"
|
|
pg "knowledge-governance/internal/platform/postgres"
|
|
"knowledge-governance/internal/qaopsorg"
|
|
"knowledge-governance/internal/qaopsquality"
|
|
"strings"
|
|
"testing"
|
|
"time"
|
|
)
|
|
|
|
func TestDashboardUsesLifetimeWeekMonthAndDeduplicatedRegionalRoster(t *testing.T) {
|
|
p := mirrorFixture(t)
|
|
ctx := context.Background()
|
|
s := NewStore(p, "fixture-dashboard")
|
|
queue := pg.NewStore(p)
|
|
dates := []string{"2026-09-28T08:00:00+08:00", "2026-10-02T08:00:00+08:00", "2026-10-06T08:00:00+08:00", "2026-10-10T08:00:00+08:00"}
|
|
source := &fixtureSource{}
|
|
for i, date := range dates {
|
|
entry := fixtureEntry([]string{"older", "month", "covered", "uncovered"}[i], "暂无已发布")
|
|
var wire map[string]any
|
|
_ = json.Unmarshal(entry.Payload, &wire)
|
|
wire["asked_at"] = date
|
|
wire["question"] = "needle 100%_ fixture"
|
|
entry.Payload, _ = json.Marshal(wire)
|
|
source.rows = append(source.rows, entry)
|
|
}
|
|
job := claimMirrorJob(t, s, queue, "dashboard-seed")
|
|
if e := s.HandleSync(ctx, *job, func(context.Context) (Source, error) { return source, nil }); e != nil {
|
|
t.Fatal(e)
|
|
}
|
|
org := qaopsorg.NewStore(p, s.tenant)
|
|
state, e := org.ReplaceDirectory(ctx, qaopsorg.Import{Source: "weekly_report_snapshot", SyncedAt: time.Date(2026, 10, 10, 0, 0, 0, 0, time.UTC), Complete: true, Directory: qaopsorg.Directory{Departments: []qaopsorg.Department{{ID: "root", Name: "示例组织"}}, People: []qaopsorg.Person{{ID: "s", DepartmentIDs: []string{"root"}}, {ID: "zero-use", DepartmentIDs: []string{"root"}}}}})
|
|
if e != nil {
|
|
t.Fatal(e)
|
|
}
|
|
_, e = org.SaveRules(ctx, qaopsorg.SaveRequest{DirectoryID: state.DirectoryID, Regions: []qaopsorg.RegionDefinition{{ID: "education", Name: "国内教育"}}, Rules: []qaopsorg.Rule{{DepartmentID: "root", Region: "education"}}}, "fixture-admin")
|
|
if e != nil {
|
|
t.Fatal(e)
|
|
}
|
|
record, e := s.GetRecord(ctx, s.tenant, "covered")
|
|
if e != nil {
|
|
t.Fatal(e)
|
|
}
|
|
quality := qaopsquality.NewStore(p, s.tenant)
|
|
views, e := quality.Observe(ctx, []dobotaiops.Record{record})
|
|
if e != nil {
|
|
t.Fatal(e)
|
|
}
|
|
v := views[record.ID]
|
|
_, e = quality.Save(ctx, record, qaopsquality.SaveRequest{InputHash: v.InputHash, InputEpoch: v.InputEpoch, BaseRevision: v.Revision, Nonce: strings.Repeat("d", 32), Fields: qaopsquality.Fields{Coverage: "covered", Correctness: "correct", ProductCategory: "CR", ProblemType: "使用咨询"}, Reason: "explicit isolated test review"}, "fixture-admin")
|
|
if e != nil {
|
|
t.Fatal(e)
|
|
}
|
|
start, _ := time.Parse(time.RFC3339, "2026-10-05T00:00:00+08:00")
|
|
end := start.AddDate(0, 0, 7)
|
|
data, e := s.ReadDashboard(ctx, dobotaiops.RecordQuery{Window: dobotaiops.Window{TenantKey: s.tenant, Start: start, End: end}, Keyword: "100%_"})
|
|
if e != nil {
|
|
t.Fatal("dashboard must read one local snapshot without a source client", e)
|
|
}
|
|
if data.TotalIssues != 4 || data.NewThisWeek != 2 || data.Quality.Total != 2 || data.Quality.Rates.Coverage.Numerator != 1 || data.Quality.Rates.Coverage.Denominator != 2 {
|
|
t.Fatal("lifetime/selected-week quality were mixed")
|
|
}
|
|
if len(data.Daily) != 7 || len(data.Regions) != 1 {
|
|
t.Fatal("daily range or custom region catalog missing")
|
|
}
|
|
r := data.Regions[0]
|
|
if r.Questions != 3 || r.Users == nil || *r.Users != 1 || r.RosterPeople == nil || *r.RosterPeople != 2 || r.Frequency == nil || *r.Frequency != 1.5 || r.UsageCoverage.Percent == nil || *r.UsageCoverage.Percent != 50 {
|
|
t.Fatal("monthly regional scope or full-roster denominator was replaced by active users")
|
|
}
|
|
if len(data.FrozenWeeks) != 2 {
|
|
t.Fatal("historical weeks did not start at first data week")
|
|
}
|
|
for _, point := range data.FrozenWeeks {
|
|
if point.FrozenAt != nil || point.Coverage.Percent != nil || point.Correctness.Percent != nil {
|
|
t.Fatal("current quality was backfilled into absent frozen history")
|
|
}
|
|
}
|
|
|
|
t.Run("historical quality is frozen once and never backfilled", func(t *testing.T) {
|
|
tracked := start.AddDate(0, 0, 2)
|
|
if e := s.TrackQualityWeeks(ctx, tracked); e != nil {
|
|
t.Fatal(e)
|
|
}
|
|
freezeAt := end.Add(6 * time.Minute)
|
|
// Explicit fake-clock fixture: the source snapshot and review existed before the closing capture.
|
|
if _, e = p.Exec(ctx, "UPDATE qa_ops_mirror_state SET source_observed_at=$2,synced_at=$2 WHERE tenant_key=$1", s.tenant, freezeAt.Add(-time.Minute)); e != nil {
|
|
t.Fatal(e)
|
|
}
|
|
if _, e = p.Exec(ctx, "UPDATE qa_ops_quality_reviews SET created_at=$2 WHERE tenant_key=$1", s.tenant, end.Add(-time.Hour)); e != nil {
|
|
t.Fatal(e)
|
|
}
|
|
if _, e = p.Exec(ctx, "UPDATE qa_ops_quality_checks SET created_at=$2 WHERE tenant_key=$1", s.tenant, end.Add(-time.Hour)); e != nil {
|
|
t.Fatal(e)
|
|
}
|
|
frozen, e := s.FreezePreviousQualityWeek(ctx, freezeAt)
|
|
if e != nil || !frozen {
|
|
t.Fatal("first eligible Monday capture was not frozen", e)
|
|
}
|
|
for _, statement := range []string{
|
|
`UPDATE qa_ops_weekly_quality_snapshots SET policy_version=policy_version||'-changed' WHERE tenant_key=$1`,
|
|
`UPDATE qa_ops_weekly_quality_snapshot_records SET question=question||' changed' WHERE tenant_key=$1`,
|
|
`DELETE FROM qa_ops_weekly_quality_snapshots WHERE tenant_key=$1`,
|
|
`DELETE FROM qa_ops_weekly_quality_snapshot_records WHERE tenant_key=$1`,
|
|
`INSERT INTO qa_ops_weekly_quality_snapshot_records SELECT tenant_key,week_start,record_id||'-late',question,coverage_numerator,coverage_denominator,correctness_numerator,correctness_denominator FROM qa_ops_weekly_quality_snapshot_records WHERE tenant_key=$1 LIMIT 1`,
|
|
} {
|
|
mutation, err := p.Begin(ctx)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
_, err = mutation.Exec(ctx, statement, s.tenant)
|
|
_ = mutation.Rollback(ctx)
|
|
var dbError *pgconn.PgError
|
|
if !errors.As(err, &dbError) || dbError.Code != "55000" {
|
|
t.Fatal("committed snapshot mutation was not rejected by the immutable guard", err)
|
|
}
|
|
}
|
|
historical, e := s.ReadDashboard(ctx, dobotaiops.RecordQuery{Window: dobotaiops.Window{TenantKey: s.tenant, Start: start, End: end}, Keyword: "100%_"})
|
|
if e != nil {
|
|
t.Fatal(e)
|
|
}
|
|
last := historical.FrozenWeeks[len(historical.FrozenWeeks)-1]
|
|
if last.FrozenAt == nil || last.Coverage.Percent == nil || *last.Coverage.Percent != 50 || last.Correctness.Percent == nil || *last.Correctness.Percent != 100 {
|
|
t.Fatal("frozen ratios or literal keyword projection were lost")
|
|
}
|
|
latest, e := quality.ReadViews(ctx, []dobotaiops.QualityKey{{ID: record.ID, InputHash: v.InputHash}})
|
|
if e != nil {
|
|
t.Fatal(e)
|
|
}
|
|
current := latest[record.ID]
|
|
if _, e = quality.Save(ctx, record, qaopsquality.SaveRequest{InputHash: current.InputHash, InputEpoch: current.InputEpoch, BaseRevision: current.Revision, Nonce: strings.Repeat("e", 32), Fields: qaopsquality.Fields{Coverage: "uncovered", Correctness: "pending"}, Reason: "later isolated correction, must not rewrite frozen history"}, "fixture-admin"); e != nil {
|
|
t.Fatal(e)
|
|
}
|
|
repeated, e := s.FreezePreviousQualityWeek(ctx, freezeAt.Add(time.Minute))
|
|
if e != nil || repeated {
|
|
t.Fatal("immutable weekly snapshot was replaced")
|
|
}
|
|
changed, e := s.ReadDashboard(ctx, dobotaiops.RecordQuery{Window: dobotaiops.Window{TenantKey: s.tenant, Start: start, End: end}, Keyword: "100%_"})
|
|
if e != nil {
|
|
t.Fatal(e)
|
|
}
|
|
point := changed.FrozenWeeks[len(changed.FrozenWeeks)-1]
|
|
if changed.Quality.Rates.Coverage.Numerator != 0 || point.Coverage.Percent == nil || *point.Coverage.Percent != 50 || changed.FrozenWeeks[0].FrozenAt != nil {
|
|
t.Fatal("current correction rewrote frozen history or backfilled pre-tracking weeks")
|
|
}
|
|
})
|
|
raw, _ := json.Marshal(data)
|
|
if strings.Contains(string(raw), "zero-use") || strings.Contains(string(raw), "needle") {
|
|
t.Fatal("dashboard leaked identity or original question rows")
|
|
}
|
|
}
|