Files
kenn-io--agentsview/internal/sync/opencode_container_gate_internal_test.go
wehub-resource-sync f99010fae1
CI / lint (push) Failing after 1s
CI / frontend (push) Failing after 1s
CI / scripts (push) Failing after 1s
CI / Go Test (ubuntu-latest) (push) Failing after 0s
CI / frontend-node-25 (push) Failing after 1s
CI / docs (push) Failing after 0s
CI / coverage (push) Failing after 0s
CI / e2e (push) Failing after 0s
Docker / build-and-push (push) Failing after 1s
CI / integration (push) Failing after 4m43s
CI / Go Test (windows-latest) (push) Has been cancelled
CI / Desktop Unit Tests (Windows) (push) Has been cancelled
Desktop Artifacts / Desktop Build (Linux (arm64)) (push) Has been cancelled
Desktop Artifacts / Desktop Build (Linux) (push) Has been cancelled
Desktop Artifacts / Desktop Build (Windows) (push) Has been cancelled
Desktop Artifacts (macOS) / Desktop Build (macOS (aarch64)) (push) Has been cancelled
Desktop Artifacts (macOS) / Desktop Build (macOS (x86_64)) (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 12:30:36 +08:00

202 lines
8.0 KiB
Go

package sync
import (
"database/sql"
"path/filepath"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"go.kenn.io/agentsview/internal/parser"
)
// newContainerTestDB creates a real SQLite file named like an OpenCode
// container, so the pass's post-discovery recapture has something to stat.
func newContainerTestDB(t *testing.T) (string, *sql.DB) {
t.Helper()
path := filepath.Join(t.TempDir(), "opencode.db")
conn, err := sql.Open("sqlite3", path)
require.NoError(t, err, "open container db")
t.Cleanup(func() { _ = conn.Close() })
_, err = conn.Exec("CREATE TABLE session (id TEXT PRIMARY KEY)")
require.NoError(t, err, "create session table")
return path, conn
}
// TestSQLiteContainerPassPromotesOnlyPreDiscoveryCaptures pins the gate's
// ordering invariant: the state promoted to trusted must have been captured
// BEFORE discovery listed the container's sessions. Discovery reads the
// session rows first, so a state captured afterwards can be newer than the
// discovered set — a session written in between would then be gate-skipped
// forever without ever being parsed. Containers with no pre-discovery
// capture must therefore never be promoted, and promoted states must be
// exactly the pre-discovery ones.
func TestSQLiteContainerPassPromotesOnlyPreDiscoveryCaptures(t *testing.T) {
t.Run("missing pre-discovery capture blocks promotion", func(t *testing.T) {
e := &Engine{}
files := []parser.DiscoveredFile{
{Agent: parser.AgentOpenCode, Path: "/data/opencode.db#ses-1"},
{Agent: parser.AgentOpenCode, Path: "/data/opencode.db#ses-2"},
}
e.beginSQLiteContainerPass(
files, map[string]parser.SQLiteContainerState{},
)
e.noteSQLiteContainerResult("/data/opencode.db#ses-1", true)
e.noteSQLiteContainerResult("/data/opencode.db#ses-2", true)
e.finishSQLiteContainerPass(false, true)
assert.Empty(t, e.trustedSQLiteContainers,
"a container without a pre-discovery capture must not be trusted")
})
t.Run("promoted state is the pre-discovery capture", func(t *testing.T) {
e := &Engine{}
dbPath, _ := newContainerTestDB(t)
pre, ok := parser.StatSQLiteContainerState(dbPath)
require.True(t, ok, "container state must be readable")
files := []parser.DiscoveredFile{
{Agent: parser.AgentOpenCode, Path: dbPath + "#ses-1"},
{Agent: parser.AgentOpenCode, Path: dbPath + "#ses-2"},
}
e.beginSQLiteContainerPass(
files,
map[string]parser.SQLiteContainerState{dbPath: pre},
)
e.noteSQLiteContainerResult(dbPath+"#ses-1", true)
e.noteSQLiteContainerResult(dbPath+"#ses-2", true)
e.finishSQLiteContainerPass(false, true)
require.Contains(t, e.trustedSQLiteContainers, dbPath)
trusted := e.trustedSQLiteContainers[dbPath]
assert.Equal(t, pre, trusted.state,
"trusted state must be exactly the pre-discovery capture")
assert.Equal(t,
map[string]struct{}{"ses-1": {}, "ses-2": {}},
trusted.sessions,
"trusted set must be exactly the verified session IDs")
})
}
// TestSQLiteContainerPassFailsOnCaptureDiscoveryMismatch pins the pass's
// recapture check: a container that changed between the pre-discovery
// capture and pass begin must neither gate-skip nor be promoted. The
// discovered session set may already include the change, so gating against
// the pre-discovery state — which still matches the trusted state — would
// skip the changed sessions for the whole pass.
func TestSQLiteContainerPassFailsOnCaptureDiscoveryMismatch(t *testing.T) {
e := &Engine{}
dbPath, conn := newContainerTestDB(t)
pre, ok := parser.StatSQLiteContainerState(dbPath)
require.True(t, ok, "container state must be readable")
// The container is trusted at the pre-discovery state, as after a
// fully verified idle pass.
e.trustedSQLiteContainers = map[string]trustedSQLiteContainer{
dbPath: {state: pre, sessions: map[string]struct{}{"ses-1": {}}},
}
// The container changes inside the capture-discovery window.
_, err := conn.Exec("INSERT INTO session (id) VALUES ('ses-1')")
require.NoError(t, err, "write session inside the window")
file := parser.DiscoveredFile{
Agent: parser.AgentOpenCode, Path: dbPath + "#ses-1",
}
e.beginSQLiteContainerPass(
[]parser.DiscoveredFile{file},
map[string]parser.SQLiteContainerState{dbPath: pre},
)
assert.False(t, e.sqliteContainerSourceFresh(file),
"a mismatched container must not gate-skip its sessions")
e.noteSQLiteContainerResult(file.Path, true)
e.finishSQLiteContainerPass(false, true)
assert.Equal(t, pre, e.trustedSQLiteContainers[dbPath].state,
"a mismatched container must not be promoted past its trusted state")
}
// TestSQLiteContainerGateParsesNewlyUnshadowedSession pins the hybrid-root
// invariant: hybrid discovery drops SQLite rows shadowed by a same-ID
// storage JSON, so the discoverable row set can grow — a storage JSON
// removed while the DB is untouched exposes its row — without the container
// state changing. Trust therefore records which session IDs the verified
// pass discovered, and only those may gate-skip; a newly exposed row was
// never verified against the archive and must parse.
func TestSQLiteContainerGateParsesNewlyUnshadowedSession(t *testing.T) {
e := &Engine{}
dbPath, _ := newContainerTestDB(t)
state, ok := parser.StatSQLiteContainerState(dbPath)
require.True(t, ok, "container state must be readable")
// A fully verified pass discovered only ses-1; ses-2's row was
// shadowed by its storage JSON at the time.
verified := parser.DiscoveredFile{
Agent: parser.AgentOpenCode, Path: dbPath + "#ses-1",
}
e.beginSQLiteContainerPass(
[]parser.DiscoveredFile{verified},
map[string]parser.SQLiteContainerState{dbPath: state},
)
e.noteSQLiteContainerResult(verified.Path, true)
e.finishSQLiteContainerPass(false, true)
require.Contains(t, e.trustedSQLiteContainers, dbPath)
// The storage JSON is removed; the DB is untouched. The next pass
// discovers ses-2's row for the first time.
exposed := parser.DiscoveredFile{
Agent: parser.AgentOpenCode, Path: dbPath + "#ses-2",
}
e.beginSQLiteContainerPass(
[]parser.DiscoveredFile{verified, exposed},
map[string]parser.SQLiteContainerState{dbPath: state},
)
assert.True(t, e.sqliteContainerSourceFresh(verified),
"the verified session must still gate-skip")
assert.False(t, e.sqliteContainerSourceFresh(exposed),
"a newly exposed row must parse despite the unchanged container")
}
// TestSQLiteContainerFullPassDropsUndiscoveredTrust pins the stale-trust
// cleanup: a complete full-discovery pass that finds no sources for a
// trusted container (fully shadowed by storage JSONs, or gone) must drop
// its trusted entry — the session set is no longer being maintained, and
// stale membership would gate-skip a row re-exposed by a later storage
// removal that leaves the DB untouched. Scoped and incomplete passes see
// only a subset of roots, so absence there proves nothing and the entry
// must survive.
func TestSQLiteContainerFullPassDropsUndiscoveredTrust(t *testing.T) {
trusted := func() map[string]trustedSQLiteContainer {
return map[string]trustedSQLiteContainer{
"/data/opencode.db": {
sessions: map[string]struct{}{"ses-1": {}},
},
}
}
t.Run("full pass drops the undiscovered container", func(t *testing.T) {
e := &Engine{}
e.trustedSQLiteContainers = trusted()
e.beginSQLiteContainerPass(nil, nil)
e.finishSQLiteContainerPass(false, true)
assert.Empty(t, e.trustedSQLiteContainers,
"a full pass with no discovered sources must drop the trust")
})
t.Run("scoped pass keeps the entry", func(t *testing.T) {
e := &Engine{}
e.trustedSQLiteContainers = trusted()
e.beginSQLiteContainerPass(nil, nil)
e.finishSQLiteContainerPass(false, false)
assert.Contains(t, e.trustedSQLiteContainers, "/data/opencode.db",
"a scoped pass must not drop trust for out-of-scope containers")
})
t.Run("incomplete pass keeps the entry", func(t *testing.T) {
e := &Engine{}
e.trustedSQLiteContainers = trusted()
e.beginSQLiteContainerPass(nil, nil)
e.finishSQLiteContainerPass(true, true)
assert.Contains(t, e.trustedSQLiteContainers, "/data/opencode.db",
"an incomplete pass must not drop any trust")
})
}