Files
knowledge-governance/internal/platform/postgres/test_publications_test.go
T
zheromiku dcdc8d1ddd feat: 知识治理平台(含已验证发布/回滚契约)
全栈知识治理平台首次入库(P1-P10 + 发布链路模拟验证闭环):

- 治理核心:源目录/变更集/人工审核/发布批次/发布执行(fail-closed 闸口)
- 测试通道:WeKnora 测试 KB 上传/轮询/验收;poll 完成时写入
  parser_generation/index_generation,修复 manual/seed 路径发布闸口永久拦截
- 发布适配器 v071(本地 source=测试角色 / 云端 target=生产角色 模拟验证通过):
  * ES 导出/导入 mapping 自适应(.keyword 子字段、可选 tenant_id)
  * OSS locator 支持嵌套 scheme;chunks.seq_id 由目标序列分配
  * bundle 归档同身份重试原子替换;verifier 按 bundle chunk_type 计数
  * 回滚 job payload 支持 prior_execution_id(strict 解码)
- 三通道发布+回滚演练实测:publish 4/4 verified,检索命中,
  rollback 4/4 rolled_back,目标端 PG is_enabled=false、ES is_enabled=false
- 前端 A·Data-console 风格全站统一,120 用例通过
- 门禁:go vet / go test 全包通过,gofmt 干净
2026-09-30 10:54:06 +08:00

602 lines
23 KiB
Go

package postgres
import (
"context"
"encoding/json"
"errors"
"strings"
"testing"
"time"
"github.com/jackc/pgx/v5"
"github.com/jackc/pgx/v5/pgconn"
"knowledge-governance/internal/governance"
)
func TestLoadPublicationForUploadMapsVerifiedCandidate(t *testing.T) {
db := &recordingDB{row: publicationUploadRow{}}
candidate, err := NewStore(db).LoadPublicationForUpload(context.Background(), "publication-1")
if err != nil {
t.Fatalf("LoadPublicationForUpload() error = %v", err)
}
if candidate.ID != "publication-1" || candidate.SourceItemVersionID != "version-1" || candidate.ContentObjectID != "content-1" {
t.Fatalf("candidate identities = %#v", candidate)
}
if candidate.TestKBID != "kb-1" || candidate.IngestState != governance.IngestPending || candidate.ProcessingState != governance.ProcessingPending {
t.Fatalf("candidate states = %#v", candidate)
}
if candidate.TargetScope != "internal" {
t.Fatalf("candidate target scope = %q", candidate.TargetScope)
}
if candidate.SHA256 != "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" || candidate.SizeBytes != 24 || candidate.ValidatedFormat != "md" || candidate.LocalSpoolRef != "objects/artifact.md" {
t.Fatalf("candidate content = %#v", candidate)
}
if db.queryRowSQL != loadPublicationForUploadSQL || len(db.queryRowArgs) != 1 || db.queryRowArgs[0] != "publication-1" {
t.Fatalf("query = %q %#v", db.queryRowSQL, db.queryRowArgs)
}
}
func TestMarkUploadingRejectsStalePublicationState(t *testing.T) {
db := &recordingDB{commandTag: pgconn.NewCommandTag("UPDATE 0")}
err := NewStore(db).MarkUploading(context.Background(), "publication-1", time.Date(2026, time.September, 4, 2, 0, 0, 0, time.UTC))
if !errors.Is(err, ErrPublicationStateConflict) {
t.Fatalf("MarkUploading() error = %v, want ErrPublicationStateConflict", err)
}
if db.execSQL != markUploadingSQL {
t.Fatalf("MarkUploading() SQL = %q", db.execSQL)
}
}
func TestRecordUploadAndSchedulePollIsAtomicAndIdempotent(t *testing.T) {
db := &recordingDB{row: sequenceRow{sequence: 1}}
now := time.Date(2026, time.September, 4, 2, 5, 0, 0, time.UTC)
nextAt := now.Add(10 * time.Second)
sequence, err := NewStore(db).RecordUploadAndSchedulePoll(context.Background(), "publication-1", "knowledge-1", now, nextAt)
if err != nil {
t.Fatalf("RecordUploadAndSchedulePoll() error = %v", err)
}
if sequence != 1 {
t.Fatalf("sequence = %d, want 1", sequence)
}
updateAt := strings.Index(recordUploadAndSchedulePollSQL, "UPDATE test_publications")
insertAt := strings.Index(recordUploadAndSchedulePollSQL, "INSERT INTO jobs")
if updateAt < 0 || insertAt < 0 || updateAt >= insertAt {
t.Fatalf("atomic SQL missing ordered update/insert: %q", recordUploadAndSchedulePollSQL)
}
if db.queryRowSQL != recordUploadAndSchedulePollSQL || len(db.queryRowArgs) != 9 {
t.Fatalf("query = %q %#v", db.queryRowSQL, db.queryRowArgs)
}
if db.queryRowArgs[8] != "test-poll:publication-1:1" {
t.Fatalf("idempotency key = %#v", db.queryRowArgs[8])
}
var payload map[string]any
if err := json.Unmarshal(db.queryRowArgs[4].([]byte), &payload); err != nil {
t.Fatal(err)
}
if len(payload) != 2 || payload["publication_id"] != "publication-1" || payload["sequence"] != float64(1) {
t.Fatalf("payload = %#v", payload)
}
}
func TestRecordRemoteStatusSchedulesNextSequenceAtomically(t *testing.T) {
db := &recordingDB{row: sequenceRow{sequence: 2}}
observedAt := time.Date(2026, time.September, 4, 2, 6, 0, 0, time.UTC)
remoteUpdatedAt := observedAt.Add(-time.Second)
nextAt := observedAt.Add(30 * time.Second)
sequence, err := NewStore(db).RecordRemoteStatusAndScheduleNextPoll(
context.Background(),
"publication-1",
1,
governance.ProcessingProcessing,
&remoteUpdatedAt,
observedAt,
nextAt,
)
if err != nil {
t.Fatalf("RecordRemoteStatusAndScheduleNextPoll() error = %v", err)
}
if sequence != 2 {
t.Fatalf("sequence = %d, want 2", sequence)
}
if !strings.Contains(recordRemoteStatusAndScheduleNextPollSQL, "poll_sequence = $2") || !strings.Contains(recordRemoteStatusAndScheduleNextPollSQL, "INSERT INTO jobs") {
t.Fatalf("SQL lacks sequence guard or atomic enqueue: %q", recordRemoteStatusAndScheduleNextPollSQL)
}
if db.queryRowSQL != recordRemoteStatusAndScheduleNextPollSQL || len(db.queryRowArgs) != 11 {
t.Fatalf("query = %q %#v", db.queryRowSQL, db.queryRowArgs)
}
if db.queryRowArgs[10] != "test-poll:publication-1:2" {
t.Fatalf("idempotency key = %#v", db.queryRowArgs[10])
}
}
func TestRecordCompletedSchedulesValidationAtomically(t *testing.T) {
db := &recordingDB{row: sequenceRow{sequence: 3}}
observedAt := time.Date(2026, time.September, 4, 2, 7, 0, 0, time.UTC)
remoteUpdatedAt := observedAt.Add(-time.Second)
err := NewStore(db).RecordCompletedAndScheduleValidation(
context.Background(), "publication-1", 3, &remoteUpdatedAt, observedAt,
)
if err != nil {
t.Fatalf("RecordCompletedAndScheduleValidation() error = %v", err)
}
if !strings.Contains(recordCompletedAndScheduleValidationSQL, "processing_state = 'completed'") || !strings.Contains(recordCompletedAndScheduleValidationSQL, "'test.validate'") {
t.Fatalf("SQL lacks completed update or validation enqueue: %q", recordCompletedAndScheduleValidationSQL)
}
if !strings.Contains(recordCompletedAndScheduleValidationSQL, "parser_generation = $11") || !strings.Contains(recordCompletedAndScheduleValidationSQL, "index_generation = $12") {
t.Fatalf("SQL lacks release generation stamping: %q", recordCompletedAndScheduleValidationSQL)
}
if db.queryRowSQL != recordCompletedAndScheduleValidationSQL || len(db.queryRowArgs) != 12 {
t.Fatalf("query = %q %#v", db.queryRowSQL, db.queryRowArgs)
}
if db.queryRowArgs[9] != "test-validate:publication-1:3" {
t.Fatalf("idempotency key = %#v", db.queryRowArgs[9])
}
if db.queryRowArgs[10] != "weknora-v0.7.1-m74@2026-09-04T02:06:59Z" || db.queryRowArgs[11] != "weknora-v0.7.1-m74@2026-09-04T02:06:59Z" {
t.Fatalf("generation args = %#v", db.queryRowArgs[10:])
}
}
func TestLoadPublicationForPollMapsOnlyProcessingProjection(t *testing.T) {
now := time.Date(2026, time.September, 4, 2, 8, 0, 0, time.UTC)
db := &recordingDB{row: publicationPollRow{now: now}}
publication, err := NewStore(db).LoadPublicationForPoll(context.Background(), "publication-1")
if err != nil {
t.Fatalf("LoadPublicationForPoll() error = %v", err)
}
if publication.ID != "publication-1" || publication.TestKBID != "kb-1" || publication.KnowledgeID != "knowledge-1" {
t.Fatalf("publication identities = %#v", publication)
}
if publication.ProcessingState != governance.ProcessingProcessing || publication.PollSequence != 2 || publication.LastRemoteStatus != "processing" {
t.Fatalf("publication state = %#v", publication)
}
if publication.LastProgressAt == nil || !publication.LastProgressAt.Equal(now) {
t.Fatalf("last progress = %#v", publication.LastProgressAt)
}
if db.queryRowSQL != loadPublicationForPollSQL {
t.Fatalf("query = %q", db.queryRowSQL)
}
}
func TestRecordProcessingFailureStoresOnlyFixedErrorCode(t *testing.T) {
now := time.Date(2026, time.September, 4, 2, 9, 0, 0, time.UTC)
for _, code := range []string{"remote detail", "https://example.com", strings.Repeat("x", 65)} {
db := &recordingDB{commandTag: pgconn.NewCommandTag("UPDATE 1")}
if err := NewStore(db).RecordProcessingFailure(context.Background(), "publication-1", 2, code, now); !errors.Is(err, ErrInvalidErrorCode) {
t.Fatalf("RecordProcessingFailure(%q) error = %v", code, err)
}
if db.execSQL != "" {
t.Fatalf("invalid code %q reached database", code)
}
}
db := &recordingDB{commandTag: pgconn.NewCommandTag("UPDATE 1")}
if err := NewStore(db).RecordProcessingFailure(context.Background(), "publication-1", 2, "remote_parse_failed", now); err != nil {
t.Fatalf("RecordProcessingFailure() error = %v", err)
}
if db.execSQL != recordProcessingFailureSQL || db.execArgs[2] != "remote_parse_failed" {
t.Fatalf("failure update = %q %#v", db.execSQL, db.execArgs)
}
}
func TestRecordUploadFailureStoresOnlyFixedErrorCode(t *testing.T) {
now := time.Date(2026, time.September, 4, 2, 9, 30, 0, time.UTC)
db := &recordingDB{commandTag: pgconn.NewCommandTag("UPDATE 1")}
if err := NewStore(db).RecordUploadFailure(context.Background(), "publication-1", "upload_identity_ambiguous", now); err != nil {
t.Fatalf("RecordUploadFailure() error = %v", err)
}
if db.execSQL != recordUploadFailureSQL || db.execArgs[1] != "upload_identity_ambiguous" {
t.Fatalf("recorded upload failure args = %#v", db.execArgs)
}
if !strings.Contains(recordUploadFailureSQL, "processing_state = 'failed'") {
t.Fatal("upload terminal failure must enter retryable processing_state=failed")
}
invalidDB := &recordingDB{commandTag: pgconn.NewCommandTag("UPDATE 1")}
if err := NewStore(invalidDB).RecordUploadFailure(context.Background(), "publication-1", "remote body: token=secret", now); !errors.Is(err, ErrInvalidErrorCode) {
t.Fatalf("invalid error code result = %v", err)
}
if invalidDB.execSQL != "" {
t.Fatal("invalid error code reached database")
}
}
func TestLoadPublicationForValidationMapsCompletedContext(t *testing.T) {
db := &recordingDB{row: publicationValidationRow{}}
context, err := NewStore(db).LoadPublicationForValidation(context.Background(), "publication-1")
if err != nil {
t.Fatalf("LoadPublicationForValidation() error = %v", err)
}
if context.ID != "publication-1" || context.TestKBID != "kb-1" || context.KnowledgeID != "knowledge-1" {
t.Fatalf("validation context = %#v", context)
}
if db.queryRowSQL != loadPublicationForValidationSQL || len(db.queryRowArgs) != 1 || db.queryRowArgs[0] != "publication-1" {
t.Fatalf("query = %q %#v", db.queryRowSQL, db.queryRowArgs)
}
}
func TestListLegacyVerificationCandidatesMapsKnowledgeIDs(t *testing.T) {
rows := &legacyVerificationRows{items: []legacyVerificationRow{
{id: "pub-1", kbID: "kb-1", knowledgeID: "knowledge-1"},
{id: "pub-2", kbID: "kb-2", knowledgeID: "knowledge-2"},
}}
db := &recordingDB{rows: rows}
candidates, err := NewStore(db).ListLegacyVerificationCandidates(context.Background())
if err != nil {
t.Fatalf("ListLegacyVerificationCandidates() error = %v", err)
}
if len(candidates) != 2 || candidates[0].ID != "pub-1" || candidates[0].KnowledgeID != "knowledge-1" || candidates[1].TestKBID != "kb-2" {
t.Fatalf("candidates = %#v", candidates)
}
if db.querySQL != listLegacyVerificationCandidatesSQL {
t.Fatalf("query = %q", db.querySQL)
}
if !strings.Contains(listLegacyVerificationCandidatesSQL, "source_type = 'pp'") {
t.Fatal("legacy verification candidates must exclude manual and future source types")
}
}
func TestListTestPublicationsReturnsSafeProjection(t *testing.T) {
now := time.Date(2026, time.September, 4, 5, 0, 0, 0, time.UTC)
rows := &testPublicationRows{items: []testPublicationRow{
{id: "pub-1", title: "Safe title", ingest: "uploaded", processing: string(governance.ProcessingFailed),
retrieval: "unpublished", targetScope: "external", changeSetID: "cs-1", changeType: "updated", canStart: false, retryCount: 2, lastError: "remote_parse_failed", canRetry: true, updated: now},
}}
db := &recordingDB{rows: rows, row: totalRow{total: 1}}
filter := governance.TestPublicationFilter{ProcessingState: string(governance.ProcessingFailed), Limit: 25, Offset: 0}
page, err := NewStore(db).ListTestPublications(context.Background(), filter)
if err != nil {
t.Fatalf("ListTestPublications() error = %v", err)
}
if page.Total != 1 || len(page.Items) != 1 {
t.Fatalf("page = %#v", page)
}
item := page.Items[0]
if item.ID != "pub-1" || item.Title != "Safe title" || item.TargetScope != "external" || item.ChangeSetID != "cs-1" || item.ChangeType != "updated" || item.CanStart || item.ProcessingState != string(governance.ProcessingFailed) || item.RetryCount != 2 || item.LastError != "remote_parse_failed" || !item.CanRetry {
t.Fatalf("item = %#v", item)
}
if db.queryRowSQL != countTestPublicationsSQL || db.querySQL != listTestPublicationsSQL {
t.Fatalf("count = %q list = %q", db.queryRowSQL, db.querySQL)
}
}
func TestListAcceptanceResultsMapsSafeProjection(t *testing.T) {
now := time.Date(2026, time.September, 4, 5, 5, 0, 0, time.UTC)
rows := &acceptanceRecordRows{items: []acceptanceRecordRow{
{checkType: "chunks_present", state: "passed", policy: "structural-v1", evidence: json.RawMessage(`{"chunk_total":2}`), observed: now},
}}
db := &recordingDB{rows: rows}
results, err := NewStore(db).ListAcceptanceResults(context.Background(), "publication-1")
if err != nil {
t.Fatalf("ListAcceptanceResults() error = %v", err)
}
if len(results) != 1 || results[0].CheckType != "chunks_present" || results[0].State != "passed" || string(results[0].EvidenceSummary) != `{"chunk_total":2}` {
t.Fatalf("results = %#v", results)
}
if db.querySQL != listAcceptanceResultsSQL || len(db.queryArgs) != 1 || db.queryArgs[0] != "publication-1" {
t.Fatalf("query = %q %#v", db.querySQL, db.queryArgs)
}
}
func TestRetryPublicationResetsAndEnqueuesUploadJob(t *testing.T) {
now := time.Date(2026, time.September, 4, 5, 10, 0, 0, time.UTC)
db := &recordingDB{
row: retryStateRow{processing: "failed", ingest: "uploaded", spoolAvailable: true},
commandTag: pgconn.NewCommandTag("INSERT 0 1"),
}
if err := NewStore(db).RetryPublication(context.Background(), "publication-1", now); err != nil {
t.Fatalf("RetryPublication() error = %v", err)
}
if db.queryRowSQL != loadRetryStateSQL || db.execSQL != retryPublicationSQL {
t.Fatalf("query = %q exec = %q", db.queryRowSQL, db.execSQL)
}
if len(db.execArgs) != 4 || db.execArgs[0] != "publication-1" {
t.Fatalf("exec args = %#v", db.execArgs)
}
}
func TestRetryPublicationRecoversLegacyUploadFailedState(t *testing.T) {
now := time.Date(2026, time.September, 5, 5, 30, 0, 0, time.UTC)
db := &recordingDB{
row: retryStateRow{processing: "pending", ingest: "failed", spoolAvailable: true},
commandTag: pgconn.NewCommandTag("INSERT 0 1"),
}
if err := NewStore(db).RetryPublication(context.Background(), "publication-1", now); err != nil {
t.Fatalf("RetryPublication() legacy upload failure error = %v", err)
}
if !strings.Contains(retryPublicationSQL, "ingest_state = 'failed'") {
t.Fatal("retry SQL must recover legacy upload-failed state")
}
}
func TestRetryPublicationRejectsCompleted(t *testing.T) {
db := &recordingDB{row: retryStateRow{processing: "completed", ingest: "uploaded", spoolAvailable: true}}
err := NewStore(db).RetryPublication(context.Background(), "publication-1", time.Date(2026, time.September, 4, 5, 11, 0, 0, time.UTC))
if !errors.Is(err, ErrPublicationStateConflict) {
t.Fatalf("RetryPublication() error = %v, want state conflict", err)
}
if db.execSQL != "" {
t.Fatal("completed publication reached the retry UPDATE")
}
}
func TestRetryPublicationRejectsMissingSpool(t *testing.T) {
db := &recordingDB{row: retryStateRow{processing: "failed", ingest: "uploaded", spoolAvailable: false}}
err := NewStore(db).RetryPublication(context.Background(), "publication-1", time.Date(2026, time.September, 4, 5, 12, 0, 0, time.UTC))
if !errors.Is(err, ErrPublicationNotRetryable) {
t.Fatalf("RetryPublication() error = %v, want not retryable", err)
}
}
func TestMarkStalledRechecksNoProgressCutoffInDatabase(t *testing.T) {
now := time.Date(2026, time.September, 4, 2, 10, 0, 0, time.UTC)
cutoff := now.Add(-30 * time.Minute)
db := &recordingDB{commandTag: pgconn.NewCommandTag("UPDATE 1")}
if err := NewStore(db).MarkStalled(context.Background(), "publication-1", 3, cutoff, now); err != nil {
t.Fatalf("MarkStalled() error = %v", err)
}
if db.execSQL != markStalledSQL || !strings.Contains(markStalledSQL, "updated_at <= $3") {
t.Fatalf("stalled SQL = %q", db.execSQL)
}
staleDB := &recordingDB{commandTag: pgconn.NewCommandTag("UPDATE 0")}
if err := NewStore(staleDB).MarkStalled(context.Background(), "publication-1", 3, cutoff, now); !errors.Is(err, ErrPublicationStateConflict) {
t.Fatalf("MarkStalled() stale error = %v", err)
}
}
func TestAppendAcceptanceResultValidatesAndAppendsEvidence(t *testing.T) {
now := time.Date(2026, time.September, 4, 2, 11, 0, 0, time.UTC)
result := governance.AcceptanceResult{
ID: "acceptance-1",
PublicationID: "publication-1",
RunID: "run-1",
CheckType: "chunk_structure",
State: governance.AcceptancePassed,
PolicyVersion: "structural-v1",
EvidenceSummary: json.RawMessage(`{"chunk_total":2,"parent_match":true}`),
ObservedAt: now,
}
db := &recordingDB{commandTag: pgconn.NewCommandTag("INSERT 0 1")}
if err := NewStore(db).AppendAcceptanceResult(context.Background(), result); err != nil {
t.Fatalf("AppendAcceptanceResult() error = %v", err)
}
if db.execSQL != appendAcceptanceResultSQL || len(db.execArgs) != 8 {
t.Fatalf("acceptance insert = %q %#v", db.execSQL, db.execArgs)
}
result.EvidenceSummary = json.RawMessage(`"raw text"`)
invalidDB := &recordingDB{commandTag: pgconn.NewCommandTag("INSERT 0 1")}
if err := NewStore(invalidDB).AppendAcceptanceResult(context.Background(), result); !errors.Is(err, ErrInvalidAcceptanceResult) {
t.Fatalf("invalid acceptance error = %v", err)
}
if invalidDB.execSQL != "" {
t.Fatal("invalid acceptance result reached database")
}
}
type publicationUploadRow struct{}
type publicationValidationRow struct{}
func (publicationValidationRow) Scan(dest ...any) error {
*(dest[0].(*string)) = "publication-1"
*(dest[1].(*string)) = "kb-1"
*(dest[2].(*string)) = "knowledge-1"
return nil
}
type legacyVerificationRow struct {
id string
kbID string
knowledgeID string
}
func (row legacyVerificationRow) Scan(dest ...any) error {
*(dest[0].(*string)) = row.id
*(dest[1].(*string)) = row.kbID
*(dest[2].(*string)) = row.knowledgeID
return nil
}
type legacyVerificationRows struct {
items []legacyVerificationRow
index int
}
func (rows *legacyVerificationRows) Close() {}
func (rows *legacyVerificationRows) Err() error { return nil }
func (rows *legacyVerificationRows) CommandTag() pgconn.CommandTag { return pgconn.CommandTag{} }
func (rows *legacyVerificationRows) FieldDescriptions() []pgconn.FieldDescription { return nil }
func (rows *legacyVerificationRows) Next() bool {
if rows.index >= len(rows.items) {
return false
}
rows.index++
return true
}
func (rows *legacyVerificationRows) Scan(dest ...any) error {
return rows.items[rows.index-1].Scan(dest...)
}
func (rows *legacyVerificationRows) Values() ([]any, error) {
return nil, errors.New("not implemented")
}
func (rows *legacyVerificationRows) RawValues() [][]byte { return nil }
func (rows *legacyVerificationRows) Conn() *pgx.Conn { return nil }
type testPublicationRow struct {
id string
title string
ingest string
processing string
retrieval string
targetScope string
changeSetID string
changeType string
canStart bool
retryCount int
lastError string
canRetry bool
updated time.Time
}
func (row testPublicationRow) Scan(dest ...any) error {
*(dest[0].(*string)) = row.id
*(dest[1].(*string)) = row.title
*(dest[2].(*string)) = row.ingest
*(dest[3].(*string)) = row.processing
*(dest[4].(*string)) = row.retrieval
*(dest[5].(*string)) = row.targetScope
*(dest[6].(*string)) = row.changeSetID
*(dest[7].(*string)) = row.changeType
*(dest[8].(*bool)) = row.canStart
*(dest[9].(*int)) = row.retryCount
*(dest[10].(*string)) = row.lastError
*(dest[11].(*bool)) = row.canRetry
*(dest[12].(*time.Time)) = row.updated
return nil
}
type testPublicationRows struct {
items []testPublicationRow
index int
}
func (rows *testPublicationRows) Close() {}
func (rows *testPublicationRows) Err() error { return nil }
func (rows *testPublicationRows) CommandTag() pgconn.CommandTag { return pgconn.CommandTag{} }
func (rows *testPublicationRows) FieldDescriptions() []pgconn.FieldDescription { return nil }
func (rows *testPublicationRows) Next() bool {
if rows.index >= len(rows.items) {
return false
}
rows.index++
return true
}
func (rows *testPublicationRows) Scan(dest ...any) error {
return rows.items[rows.index-1].Scan(dest...)
}
func (rows *testPublicationRows) Values() ([]any, error) { return nil, errors.New("not implemented") }
func (rows *testPublicationRows) RawValues() [][]byte { return nil }
func (rows *testPublicationRows) Conn() *pgx.Conn { return nil }
type totalRow struct {
total int
}
func (row totalRow) Scan(dest ...any) error {
*(dest[0].(*int)) = row.total
return nil
}
type acceptanceRecordRow struct {
checkType string
state string
policy string
evidence json.RawMessage
observed time.Time
}
func (row acceptanceRecordRow) Scan(dest ...any) error {
*(dest[0].(*string)) = row.checkType
*(dest[1].(*string)) = row.state
*(dest[2].(*string)) = row.policy
*(dest[3].(*json.RawMessage)) = row.evidence
*(dest[4].(*time.Time)) = row.observed
return nil
}
type acceptanceRecordRows struct {
items []acceptanceRecordRow
index int
}
func (rows *acceptanceRecordRows) Close() {}
func (rows *acceptanceRecordRows) Err() error { return nil }
func (rows *acceptanceRecordRows) CommandTag() pgconn.CommandTag { return pgconn.CommandTag{} }
func (rows *acceptanceRecordRows) FieldDescriptions() []pgconn.FieldDescription { return nil }
func (rows *acceptanceRecordRows) Next() bool {
if rows.index >= len(rows.items) {
return false
}
rows.index++
return true
}
func (rows *acceptanceRecordRows) Scan(dest ...any) error {
return rows.items[rows.index-1].Scan(dest...)
}
func (rows *acceptanceRecordRows) Values() ([]any, error) { return nil, errors.New("not implemented") }
func (rows *acceptanceRecordRows) RawValues() [][]byte { return nil }
func (rows *acceptanceRecordRows) Conn() *pgx.Conn { return nil }
type retryStateRow struct {
processing string
ingest string
spoolAvailable bool
}
func (row retryStateRow) Scan(dest ...any) error {
*(dest[0].(*string)) = row.processing
*(dest[1].(*string)) = row.ingest
*(dest[2].(*bool)) = row.spoolAvailable
return nil
}
type publicationPollRow struct {
now time.Time
}
func (row publicationPollRow) Scan(dest ...any) error {
*(dest[0].(*string)) = "publication-1"
*(dest[1].(*string)) = "kb-1"
*(dest[2].(*string)) = "knowledge-1"
*(dest[3].(*string)) = string(governance.ProcessingProcessing)
*(dest[4].(*int)) = 2
*(dest[5].(*string)) = "processing"
*(dest[6].(**time.Time)) = &row.now
*(dest[7].(**time.Time)) = &row.now
*(dest[8].(**time.Time)) = &row.now
*(dest[9].(**time.Time)) = &row.now
return nil
}
type sequenceRow struct {
sequence int
}
func (row sequenceRow) Scan(dest ...any) error {
*(dest[0].(*int)) = row.sequence
return nil
}
func (publicationUploadRow) Scan(dest ...any) error {
values := []any{
"publication-1",
"version-1",
"kb-1",
"",
string(governance.IngestPending),
string(governance.ProcessingPending),
"internal",
"content-1",
"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
int64(24),
"text/markdown",
"md",
"objects/artifact.md",
"Safe title",
}
for index, value := range values {
switch target := dest[index].(type) {
case *string:
*target = value.(string)
case *int64:
*target = value.(int64)
case *governance.Visibility:
*target = governance.Visibility(value.(string))
case nil:
panic("nil destination")
default:
panic("unexpected scan destination")
}
}
return nil
}