WeKnora deployment source with the reviewed wiki identity, summary and recovery fixes
Anydoc / anydoc parser engine (push) Canceled after 0s
App / Format, vet, test, and build (push) Canceled after 0s
CLI / test (macos-latest / go 1.26) (push) Canceled after 0s
CLI / test (ubuntu-latest / go 1.26) (push) Canceled after 0s
CLI / test (windows-latest / go 1.26) (push) Canceled after 0s
Build and Push Docker Image / build-ui (push) Canceled after 0s
Build and Push Docker Image / build-docreader (push) Canceled after 0s
Build and Push Docker Image / build-sandbox (push) Canceled after 0s
Build and Push Docker Image / build-app (amd64, linux/amd64, ubuntu-latest) (push) Canceled after 0s
Build and Push Docker Image / build-app (arm64, linux/arm64, ubuntu-24.04-arm) (push) Canceled after 0s
DocReader / Python tests and Go client tests (push) Canceled after 0s
Build & Publish (dsh-weknora) / Typecheck, test, pack (push) Canceled after 0s
Build & Publish (dsh-weknora) / End-to-end inside dsh (pinned) (push) Canceled after 0s
Build & Publish (dsh-weknora) / End-to-end inside dsh (latest, advisory) (push) Canceled after 0s
Frontend / Test, type-check, and build (push) Canceled after 0s
Go Lint Cache / warm lint cache (.) (push) Canceled after 0s
Go Lint Cache / warm lint cache (cli) (push) Canceled after 0s
Go Lint Cache / warm lint cache (client) (push) Canceled after 0s
Build & Publish (mcp-server) / Test (Python 3.10) (push) Canceled after 0s
Build & Publish (mcp-server) / Test (Python 3.11) (push) Canceled after 0s
Build & Publish (mcp-server) / Test (Python 3.12) (push) Canceled after 0s
Build & Publish (mcp-server) / Test (Python 3.13) (push) Canceled after 0s
Build and Push Docker Image / merge (push) Canceled after 0s
Build & Publish (dsh-weknora) / Publish to npm (push) Canceled after 0s
Build & Publish (mcp-server) / Build sdist + wheel (push) Canceled after 0s
Build & Publish (mcp-server) / Publish to PyPI (push) Canceled after 0s

This commit is contained in:
yawan
2026-09-22 16:19:06 +08:00
commit f7fc992f57
3929 changed files with 1006607 additions and 0 deletions
@@ -0,0 +1,219 @@
package service
import (
"context"
"testing"
"github.com/stretchr/testify/require"
"gorm.io/driver/sqlite"
"gorm.io/gorm"
"github.com/Tencent/WeKnora/internal/sandbox"
"github.com/Tencent/WeKnora/internal/types"
)
type pinTestManager struct {
typ sandbox.SandboxType
}
func (m *pinTestManager) Execute(context.Context, *sandbox.ExecuteConfig) (*sandbox.ExecuteResult, error) {
return nil, nil
}
func (m *pinTestManager) Cleanup(context.Context) error { return nil }
func (m *pinTestManager) GetSandbox() sandbox.Sandbox { return nil }
func (m *pinTestManager) GetType() sandbox.SandboxType { return m.typ }
func newPinTestDB(t *testing.T) *gorm.DB {
t.Helper()
db, err := gorm.Open(sqlite.Open("file:"+t.Name()+"?mode=memory&cache=shared"), &gorm.Config{})
require.NoError(t, err)
require.NoError(t, db.Migrator().DropTable(&types.Session{}))
require.NoError(t, db.AutoMigrate(&types.Session{}))
require.NoError(t, db.Model(&types.Session{}).Create(map[string]any{
"id": "s-1",
"tenant_id": uint64(7),
}).Error)
return db
}
func TestPinWritesWhenUnset(t *testing.T) {
pinner := NewSessionSandboxPinner(newPinTestDB(t))
ctx := context.Background()
got, err := pinner.Pin(ctx, "s-1", "cfg-a")
require.NoError(t, err)
require.Equal(t, "cfg-a", got)
read, err := pinner.Read(ctx, "s-1")
require.NoError(t, err)
require.Equal(t, "cfg-a", read)
}
// Two concurrent first-sandbox creations must converge on one config, or the
// session would end up with two sandboxes on two backends.
func TestPinIsIdempotentAndReturnsExistingWinner(t *testing.T) {
pinner := NewSessionSandboxPinner(newPinTestDB(t))
ctx := context.Background()
first, err := pinner.Pin(ctx, "s-1", "cfg-a")
require.NoError(t, err)
require.Equal(t, "cfg-a", first)
second, err := pinner.Pin(ctx, "s-1", "cfg-b")
require.NoError(t, err)
require.Equal(t, "cfg-a", second, "the first writer wins; later callers adopt it")
}
func TestPinLeavesEmptyConfigUnpinned(t *testing.T) {
pinner := NewSessionSandboxPinner(newPinTestDB(t))
ctx := context.Background()
got, err := pinner.Pin(ctx, "s-1", "")
require.NoError(t, err)
require.Empty(t, got)
}
// A padded ID must land in the column exactly as Read will compare it, or the
// conditional claim would never recognise its own write.
func TestPinTrimsConfigIDBeforeStoring(t *testing.T) {
pinner := NewSessionSandboxPinner(newPinTestDB(t))
ctx := context.Background()
got, err := pinner.Pin(ctx, "s-1", " cfg-a ")
require.NoError(t, err)
require.Equal(t, "cfg-a", got)
read, err := pinner.Read(ctx, "s-1")
require.NoError(t, err)
require.Equal(t, "cfg-a", read)
}
// Pin runs right after a sandbox was created, so a vanished session must be an
// error: "" would read as "no live sandbox" and abandon a real one.
func TestPinFailsWhenSessionIsGone(t *testing.T) {
db := newPinTestDB(t)
pinner := NewSessionSandboxPinner(db)
ctx := context.Background()
_, err := pinner.Pin(ctx, "missing", "cfg-a")
require.ErrorIs(t, err, gorm.ErrRecordNotFound)
require.NoError(t, db.Delete(&types.Session{}, "id = ?", "s-1").Error)
_, err = pinner.Pin(ctx, "s-1", "cfg-a")
require.ErrorIs(t, err, gorm.ErrRecordNotFound,
"a soft-deleted session already had its sandbox destroyed")
}
// Read keeps the lenient contract: absent session and unpinned session are
// both simply "no live sandbox".
func TestReadReportsNoSandboxForMissingSession(t *testing.T) {
pinner := NewSessionSandboxPinner(newPinTestDB(t))
read, err := pinner.Read(context.Background(), "missing")
require.NoError(t, err)
require.Empty(t, read)
}
// The pin dies with the sandbox: after teardown the session must be free to
// follow its agent's CURRENT backend choice.
func TestClearReleasesPin(t *testing.T) {
pinner := NewSessionSandboxPinner(newPinTestDB(t))
ctx := context.Background()
_, err := pinner.Pin(ctx, "s-1", "cfg-a")
require.NoError(t, err)
require.NoError(t, pinner.Clear(ctx, "s-1"))
read, err := pinner.Read(ctx, "s-1")
require.NoError(t, err)
require.Empty(t, read)
}
// Session delete soft-deletes the row before any follow-up work. Destroy must
// therefore read sandbox_config_id first; otherwise the pinner sees an absent
// session and teardown resolves the wrong backend (T4 regression).
func TestSoftDeleteHidesSandboxPin(t *testing.T) {
db := newPinTestDB(t)
pinner := NewSessionSandboxPinner(db)
ctx := context.Background()
_, err := pinner.Pin(ctx, "s-1", "cfg-a")
require.NoError(t, err)
require.NoError(t, db.Delete(&types.Session{}, "id = ?", "s-1").Error)
read, err := pinner.Read(ctx, "s-1")
require.NoError(t, err)
require.Empty(t, read, "soft-deleted session must not expose its pin")
}
func TestResolveSandboxForExecutionDoesNotPinStatelessBackend(t *testing.T) {
pinner := NewSessionSandboxPinner(newPinTestDB(t))
want := &pinTestManager{typ: sandbox.SandboxTypeDisabled}
got, configID, err := resolveSandboxForExecution(
context.Background(), stubSandboxResolver{mgr: want}, nil, pinner,
7, "s-1", "cfg-local", nil,
)
require.NoError(t, err)
require.Same(t, want, got)
require.Equal(t, "cfg-local", configID)
pinned, err := pinner.Read(context.Background(), "s-1")
require.NoError(t, err)
require.Empty(t, pinned, "disabled backends must not leave a session binding")
}
func TestResolveSandboxForExecutionPinsRemoteBackend(t *testing.T) {
pinner := NewSessionSandboxPinner(newPinTestDB(t))
want := &pinTestManager{typ: sandbox.SandboxTypeCube}
got, configID, err := resolveSandboxForExecution(
context.Background(), stubSandboxResolver{mgr: want}, nil, pinner,
7, "s-1", "cfg-cube", nil,
)
require.NoError(t, err)
require.Same(t, want, got)
require.Equal(t, "cfg-cube", configID)
pinned, err := pinner.Read(context.Background(), "s-1")
require.NoError(t, err)
require.Equal(t, "cfg-cube", pinned)
}
// Docker is a session-persistent remote backend, same as Cube/E2B. Skipping
// the pin used to make ArtifactCollector treat the turn as "no live sandbox"
// and leave generated HTML/files showing as unavailable in chat.
func TestResolveSandboxForExecutionPinsDockerBackend(t *testing.T) {
pinner := NewSessionSandboxPinner(newPinTestDB(t))
want := &pinTestManager{typ: sandbox.SandboxTypeDocker}
got, configID, err := resolveSandboxForExecution(
context.Background(), stubSandboxResolver{mgr: want}, nil, pinner,
7, "s-1", "cfg-docker", nil,
)
require.NoError(t, err)
require.Same(t, want, got)
require.Equal(t, "cfg-docker", configID)
pinned, err := pinner.Read(context.Background(), "s-1")
require.NoError(t, err)
require.Equal(t, "cfg-docker", pinned)
}
func TestResolveSandboxForExecutionKeepsExistingRemotePin(t *testing.T) {
pinner := NewSessionSandboxPinner(newPinTestDB(t))
_, err := pinner.Pin(context.Background(), "s-1", "cfg-existing")
require.NoError(t, err)
want := &pinTestManager{typ: sandbox.SandboxTypeE2B}
got, configID, err := resolveSandboxForExecution(
context.Background(), stubSandboxResolver{mgr: want}, nil, pinner,
7, "s-1", "cfg-new-agent-choice", nil,
)
require.NoError(t, err)
require.Same(t, want, got)
require.Equal(t, "cfg-existing", configID,
"re-pointing an agent must not move an existing remote session")
}