Files
sanderling/internal/verifier/action_encoding_test.go
T
pj 81713fa183 feat(verifier): refuse a bundle built against a different action encoding
ActionWireContract names the encoding wireAction decodes and must equal the one
the bundled runtime entry declares. An absent declaration is a mismatch, not a
default: it is the package that shipped the zero-distance scroll. A bundle that
installs no picker generates no actions and is exempt.
2026-08-22 21:04:23 +05:30

126 lines
4.3 KiB
Go

package verifier
import (
"os"
"path/filepath"
"strings"
"testing"
"github.com/priyanshujain/sanderling/internal/bundler"
)
// legacyRuntimeEntry stands in for the @sanderling/spec 0.0.3 runtime entry:
// it installs the picker and declares nothing, and its authored Scroll carries
// the container's own point as both endpoints. Paired with a binary that
// treats pre-computed endpoints as authoritative, every scroll it produces is
// a drag from a point to itself.
const legacyRuntimeEntry = `
const target = globalThis as Record<string, unknown>;
Object.defineProperty(target, "__sanderlingNextAction__", {
value: () => ({
kind: "Scroll",
direction: "down",
fromX: 540,
fromY: 1200,
toX: 540,
toY: 1200,
durationMillis: 250,
selector: "id:ledger",
}),
writable: false,
configurable: false,
enumerable: false,
});
`
func bundleWithRuntimeEntry(t *testing.T, runtimeSource string) string {
t.Helper()
dir := t.TempDir()
specPath := filepath.Join(dir, "spec.ts")
if err := os.WriteFile(specPath, []byte("globalThis.properties = {};\n"), 0o600); err != nil {
t.Fatal(err)
}
runtimePath := filepath.Join(dir, "legacy-runtime.ts")
if err := os.WriteFile(runtimePath, []byte(runtimeSource), 0o600); err != nil {
t.Fatal(err)
}
bundle, err := bundler.Bundle(bundler.Options{EntryFile: specPath, RuntimeFile: runtimePath})
if err != nil {
t.Fatal(err)
}
return string(bundle.JavaScript)
}
func TestLoad_RefusesABundleThatDeclaresNoActionEncoding(t *testing.T) {
verifier := newVerifier(t)
err := verifier.Load(bundleWithRuntimeEntry(t, legacyRuntimeEntry))
if err == nil {
t.Fatal("Load accepted a bundle that declares no action encoding; a spec " +
"bundled by a package older than this binary dispatches every scroll " +
"successfully and travels zero distance, and the run reports nothing")
}
message := err.Error()
for _, want := range []string{ActionWireContract, "same commit"} {
if !strings.Contains(message, want) {
t.Errorf("Load error %q does not mention %q; the failure has to name "+
"both encodings and what to do about it", message, want)
}
}
}
func TestLoad_RefusesABundleThatDeclaresADifferentActionEncoding(t *testing.T) {
verifier := newVerifier(t)
runtime := strings.Replace(legacyRuntimeEntry,
`const target = globalThis as Record<string, unknown>;`,
`const target = globalThis as Record<string, unknown>;
target.__sanderlingActionEncoding__ = "action-wire/1";`, 1)
err := verifier.Load(bundleWithRuntimeEntry(t, runtime))
if err == nil {
t.Fatal("Load accepted a bundle built against a different action encoding")
}
if !strings.Contains(err.Error(), "action-wire/1") {
t.Errorf("Load error %q does not name the encoding the spec was built "+
"against", err.Error())
}
}
// The declaration the shipped runtime entry makes and the one this binary
// decodes are one contract. When they drift apart every run refuses to start,
// so the drift has to be reported here rather than as a bundling failure in
// every other suite.
func TestLoad_ShippedRuntimeEntryDeclaresTheContractThisBinaryImplements(t *testing.T) {
verifier := newVerifier(t)
loadActionSpec(t, verifier, `
import { actions, Wait } from "@sanderling/spec";
globalThis.actions = actions(Wait({ durationMillis: 1 }));
globalThis.properties = {};
`)
declared := verifier.runtime.GlobalObject().Get(actionEncodingGlobal)
if declared == nil || declared.String() != ActionWireContract {
t.Fatalf("pkg/spec/src/runtime-entry.ts declares %v, this binary implements %q",
declared, ActionWireContract)
}
}
// pickerFreeRuntimeEntry stands in for a bundle that registers properties and
// generates no actions: a raw-JS fixture, or the bundle-check tool, which
// bundles with no runtime entry at all.
const pickerFreeRuntimeEntry = `
const target = globalThis as Record<string, unknown>;
target.__sanderlingBundleCheck__ = true;
`
// A bundle with no picker has no encoding to disagree about, so demanding a
// declaration from it would refuse to load specs that were never going to
// dispatch anything. chrome.Driver.InstallBundle applies the same rule.
func TestLoad_AcceptsABundleThatInstallsNoPicker(t *testing.T) {
verifier := newVerifier(t)
if err := verifier.Load(bundleWithRuntimeEntry(t, pickerFreeRuntimeEntry)); err != nil {
t.Fatalf("Load refused a bundle that generates no actions: %v", err)
}
}