Files
multica/server/cmd/migrate/migrate_source_context_test.go
T
686957e743 MUL-7271: test(server): cut slow Go test time further (follow-up to #8295) (#8318)
* test(server): reduce slow unit test waits

Co-authored-by: multica-agent <[email protected]>

* test(server): cut slow Go unit test time further

Follow-up to the first pass on MUL-7271. Test-only changes plus a few
package-private seams whose production values are unchanged.

- Re-executed race-built test binaries no longer pay the race runtime's 1s
  atexit sleep (GORACE=atexit_sleep_ms=0 for child processes only).
- pkg/agent: event-driven or shorter test-only bounds for handshake, drain
  and idle graces; redundant timeout variants merged.
- daemon/execenv: git fixtures copied from a template repo built once; tests
  that re-ran the same worktree lifecycle folded into their supersets;
  ForkLock-guarded fake-CLI writes so parallel tests cannot hit ETXTBSY.
- handler/service: cap and paging tests shrink their caps instead of seeding
  thousands of rows; lock waits probe pg_locks instead of sleeping; one
  shared pgxpool per service test binary; cheaper cron and RSA fixtures.
- wecom: the phishing-link sweep reuses one parser and derives CRLF from LF
  with a sampled cross-check; fake sockets acknowledge instead of waiting out
  the ack timeout.
- migrate: the migration corpus is parsed once; isolated DB tests run in
  parallel.

Co-authored-by: multica-agent <[email protected]>

* test(server): remove transport timeout unit tests

Co-authored-by: multica-agent <[email protected]>

* test(server): restore two non-timeout tests and make a bound load-bearing

Follow-up to the review of #8318. All test-only; no production code changes.

- daemon: restore the three read-liveness cases that the transport-timeout
  sweep took with it. They assert that a ping, a pong and an application
  frame each EXTEND the read deadline, so a healthy wakeup socket survives
  without traffic of our own — the deadline never fires on the happy path,
  which is why they are not transport-timeout tests. Verified they fail
  (all three) when the extension calls are removed from wakeup.go, and pass
  in 0.57s together. The pure "times out without peer traffic" case stays
  deleted.

- pkg/agent: restore the parent-DEADLINE half of the opaque-env redaction
  pair. codex.go treats a parent deadline and a parent cancellation as one
  ending; only the Canceled arm was left covered. The fake now writes the
  secret to stderr before reading the handshake, so the check no longer
  races our own initialize write, and the budget is 2s against a measured
  430-465ms launch. Verified it fails when the !contextEnded guard is
  removed.

- pkg/agent: TestRunCollectQuietDoesNotWaitWhenTheAnswerIsIn had its bound
  widened to a literal 5s, which the shrunk 750ms drain grace can never
  reach — the test could no longer fail. It now raises the drain grace to
  10s for its own duration and asserts under 3s: healthy runs take
  0.19-0.44s, and a broken short-circuit takes 10.19s and fails.

- wecom: gofmt the import block in relay_settle_budget_test.go.

Co-authored-by: multica-agent <[email protected]>

---------

Co-authored-by: Sol-Boy <[email protected]>
Co-authored-by: multica-agent <[email protected]>
Co-authored-by: J <[email protected]>
2026-09-14 14:33:50 +08:00

258 lines
8.8 KiB
Go

package main
import (
"context"
"fmt"
"math/rand/v2"
"strings"
"testing"
"time"
"github.com/jackc/pgx/v5"
)
func TestSourceContextMigrationsRollbackFailsClosedWithCapturedData(t *testing.T) {
t.Parallel()
adminPool := openTestPool(t)
ctx, cancel := context.WithTimeout(context.Background(), 90*time.Second)
defer cancel()
suffix := fmt.Sprintf("%d_%d", time.Now().UnixNano(), rand.Uint32())
schema := "migrate_source_context_" + suffix
schemaIdent := pgx.Identifier{schema}.Sanitize()
if _, err := adminPool.Exec(ctx, "CREATE SCHEMA "+schemaIdent); err != nil {
t.Fatalf("create schema: %v", err)
}
t.Cleanup(func() {
cleanupCtx, cleanupCancel := context.WithTimeout(context.Background(), 30*time.Second)
defer cleanupCancel()
if _, err := adminPool.Exec(cleanupCtx, "DROP SCHEMA IF EXISTS "+schemaIdent+" CASCADE"); err != nil {
t.Logf("drop schema %s: %v", schema, err)
}
})
pool := openTestPoolWithSearchPath(t, schema)
if _, err := pool.Exec(ctx, `CREATE TABLE attachment (id UUID NOT NULL)`); err != nil {
t.Fatalf("create attachment fixture: %v", err)
}
versions := []string{
"407_issue_source_context",
"408_issue_source_context_id_index",
"409_issue_source_context_issue_index",
"410_issue_source_context_origin_task_index",
"411_attachment_source_context_index",
"412_issue_source_context_object_intent_key_index",
"413_issue_source_context_object_intent_due_index",
"414_issue_source_context_object_intent_context_index",
}
options := runOptions{
Direction: "up",
Files: realMigrationFiles(t, versions, "up"),
SchemaMigrationsTable: schema + ".schema_migrations",
AdvisoryLockKey: int64(rand.Uint64()&0x7fffffffffffffff) | 1,
Hooks: hooksForDirection("up"),
}
if err := runMigrations(ctx, pool, options); err != nil {
t.Fatalf("apply source-context migrations: %v", err)
}
assertSourceContextSchema(t, pool, schema, true)
assertSourceContextIndexes(t, pool, schema, true)
assertSourceContextMigrationLedger(t, pool, schema, versions, true)
if _, err := pool.Exec(ctx, `
INSERT INTO issue_source_context (
id, workspace_id, issue_id, origin_task_id, source_issue_id,
anchor_comment_id, captured_by_user_id, snapshot_version,
snapshot, capture_digest, state, attached_at
) VALUES (
'00000000-0000-0000-0000-000000000001',
'00000000-0000-0000-0000-000000000002',
'00000000-0000-0000-0000-000000000003',
NULL,
'00000000-0000-0000-0000-000000000004',
'00000000-0000-0000-0000-000000000005',
'00000000-0000-0000-0000-000000000006',
1,
'{}'::jsonb,
'digest',
'attached',
now()
)
`); err != nil {
t.Fatalf("insert source-context fixture: %v", err)
}
if _, err := pool.Exec(ctx, `
INSERT INTO attachment (id, source_context_id)
VALUES (
'00000000-0000-0000-0000-000000000007',
'00000000-0000-0000-0000-000000000001'
)
`); err != nil {
t.Fatalf("insert source-context attachment fixture: %v", err)
}
if _, err := pool.Exec(ctx, `
INSERT INTO issue_source_context_object_intent (
storage_key, workspace_id, source_context_id, attachment_id, object_url
) VALUES (
'workspaces/test/source-context/snapshot.txt',
'00000000-0000-0000-0000-000000000002',
'00000000-0000-0000-0000-000000000001',
'00000000-0000-0000-0000-000000000007',
'https://objects.example/source-context/snapshot.txt'
)
`); err != nil {
t.Fatalf("insert source-context object intent fixture: %v", err)
}
reversed := append([]string(nil), versions...)
for left, right := 0, len(reversed)-1; left < right; left, right = left+1, right-1 {
reversed[left], reversed[right] = reversed[right], reversed[left]
}
options.Direction = "down"
options.Files = realMigrationFiles(t, reversed, "down")
options.Hooks = hooksForDirection("down")
err := runMigrations(ctx, pool, options)
if err == nil || !strings.Contains(err.Error(), "cannot roll back issue source context") {
t.Fatalf("rollback with captured data error = %v, want fail-closed source-context error", err)
}
assertSourceContextSchema(t, pool, schema, true)
assertSourceContextIndexes(t, pool, schema, true)
assertSourceContextMigrationLedger(t, pool, schema, versions, true)
for table, want := range map[string]int{
"issue_source_context": 1,
"issue_source_context_object_intent": 1,
"attachment": 1,
} {
var count int
if err := pool.QueryRow(ctx, "SELECT count(*) FROM "+table).Scan(&count); err != nil {
t.Fatalf("count %s after rejected rollback: %v", table, err)
}
if count != want {
t.Fatalf("%s rows after rejected rollback = %d, want %d", table, count, want)
}
}
if _, err := pool.Exec(ctx, `
DELETE FROM issue_source_context_object_intent;
DELETE FROM attachment WHERE source_context_id IS NOT NULL;
DELETE FROM issue_source_context;
`); err != nil {
t.Fatalf("clean source-context fixtures before retry: %v", err)
}
if err := runMigrations(ctx, pool, options); err != nil {
t.Fatalf("roll back source-context migrations after cleanup: %v", err)
}
assertSourceContextSchema(t, pool, schema, false)
assertSourceContextIndexes(t, pool, schema, false)
assertSourceContextMigrationLedger(t, pool, schema, versions, false)
var attachmentExists bool
if err := pool.QueryRow(ctx, `SELECT to_regclass($1) IS NOT NULL`, schema+".attachment").Scan(&attachmentExists); err != nil {
t.Fatalf("read attachment table existence after rollback: %v", err)
}
if !attachmentExists {
t.Fatal("attachment table was removed by source-context rollback")
}
options.Direction = "up"
options.Files = realMigrationFiles(t, versions, "up")
options.Hooks = hooksForDirection("up")
if err := runMigrations(ctx, pool, options); err != nil {
t.Fatalf("reapply source-context migrations: %v", err)
}
assertSourceContextSchema(t, pool, schema, true)
var count int
if err := pool.QueryRow(ctx, `SELECT count(*) FROM issue_source_context`).Scan(&count); err != nil {
t.Fatalf("count source contexts after reapply: %v", err)
}
if count != 0 {
t.Fatalf("source contexts after guarded rollback and reapply = %d, want 0", count)
}
}
func assertSourceContextSchema(t *testing.T, pool interface {
QueryRow(context.Context, string, ...any) pgx.Row
}, schema string, want bool) {
t.Helper()
ctx := context.Background()
for _, relation := range []string{"issue_source_context", "issue_source_context_object_intent"} {
var exists bool
if err := pool.QueryRow(ctx, `SELECT to_regclass($1) IS NOT NULL`, schema+"."+relation).Scan(&exists); err != nil {
t.Fatalf("read %s existence: %v", relation, err)
}
if exists != want {
t.Fatalf("%s existence = %v, want %v", relation, exists, want)
}
}
var columnExists bool
if err := pool.QueryRow(ctx, `
SELECT EXISTS (
SELECT 1
FROM information_schema.columns
WHERE table_schema = $1
AND table_name = 'attachment'
AND column_name = 'source_context_id'
)
`, schema).Scan(&columnExists); err != nil {
t.Fatalf("read attachment.source_context_id existence: %v", err)
}
if columnExists != want {
t.Fatalf("attachment.source_context_id existence = %v, want %v", columnExists, want)
}
}
func assertSourceContextIndexes(t *testing.T, pool interface {
QueryRow(context.Context, string, ...any) pgx.Row
}, schema string, want bool) {
t.Helper()
ctx := context.Background()
for _, index := range []string{
"idx_issue_source_context_id",
"idx_issue_source_context_issue",
"idx_issue_source_context_origin_task",
"idx_attachment_source_context",
"idx_issue_source_context_object_intent_key",
"idx_issue_source_context_object_intent_due",
"idx_issue_source_context_object_intent_context",
} {
var usable bool
if err := pool.QueryRow(ctx, `
SELECT EXISTS (
SELECT 1
FROM pg_class index_relation
JOIN pg_namespace namespace ON namespace.oid = index_relation.relnamespace
JOIN pg_index index_state ON index_state.indexrelid = index_relation.oid
WHERE namespace.nspname = $1
AND index_relation.relname = $2
AND index_state.indisvalid
AND index_state.indisready
)
`, schema, index).Scan(&usable); err != nil {
t.Fatalf("read %s usability: %v", index, err)
}
if usable != want {
t.Fatalf("%s usable = %v, want %v", index, usable, want)
}
}
}
func assertSourceContextMigrationLedger(t *testing.T, pool interface {
QueryRow(context.Context, string, ...any) pgx.Row
}, schema string, versions []string, want bool) {
t.Helper()
ctx := context.Background()
table := pgx.Identifier{schema, "schema_migrations"}.Sanitize()
for _, version := range versions {
var recorded bool
if err := pool.QueryRow(ctx, "SELECT EXISTS (SELECT 1 FROM "+table+" WHERE version = $1)", version).Scan(&recorded); err != nil {
t.Fatalf("read migration ledger entry %s: %v", version, err)
}
if recorded != want {
t.Fatalf("migration ledger entry %s recorded = %v, want %v", version, recorded, want)
}
}
}