pluginsdk/plugin/wasmguest/bnwasm/tx_expired_test.go
Alex Dunmow b6d40ed8ac feat: bootstrap block/pluginsdk — the proto-first plugin SDK (P1)
Bootstrap the plugin-facing SDK module so the fleet migration (P2) becomes a
mechanical block/core/X -> block/pluginsdk/X import rewrite.

- abi/proto/v1: the single source-of-truth ABI proto tree, copied from the cms
  authoring source (cms/backend/abi/proto/v1) with go_package retargeted to
  git.dev.alexdunmow.com/block/pluginsdk/abi/v1;abiv1. Kills the hand-kept
  cms/core proto duplication (audit gap 4).
- abi/v1: generated Go bindings (buf generate); byte-identical to core's abi/v1
  save the embedded go_package path.
- plugin/ (registration + DI surface), plugin/wasmguest/** (transport shim,
  caps stubs, bnwasm db driver, testdata fixtures + golden .pb), and the
  guest-facing type packages: blocks (+builtin/shared/tags), templates
  (+pongo/bn), auth, settings, content, gating, crypto, rbac, video, ai,
  subscriptions, menus, datasources. Internal imports rewritten core ->
  pluginsdk; zero block/core references remain.
- README/AGENTS(+CLAUDE symlink)/Makefile: proto is the contract, Go is one
  binding; no replace directives; templates/bn is a synced copy authored in cms.

Spec: cms docs/superpowers/specs/2026-07-07-proto-first-plugin-sdk-design.md

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-07 10:51:00 +08:00

75 lines
2.4 KiB
Go

package bnwasm
import (
"context"
"errors"
"testing"
abiv1 "git.dev.alexdunmow.com/block/pluginsdk/abi/v1"
"google.golang.org/protobuf/proto"
)
// fakeHostErr mirrors *wasmguest.HostError's AbiErrorCode() method so the
// transport's cross-boundary classification (mapTransportErr) can be exercised
// natively, without importing the wasip1-only wasmguest package.
type fakeHostErr struct {
code abiv1.AbiErrorCode
msg string
}
func (e *fakeHostErr) Error() string { return e.msg }
func (e *fakeHostErr) AbiErrorCode() abiv1.AbiErrorCode { return e.code }
// transportReturning builds a Transport whose every call fails with err.
func transportReturning(err error) Transport {
return func(_ string, _, _ proto.Message) error { return err }
}
func TestTransportMapsTxExpiredToSentinel(t *testing.T) {
hostErr := &fakeHostErr{code: abiv1.AbiErrorCode_ABI_ERROR_CODE_TX_EXPIRED, msg: "host: tx handle expired"}
tr := transportReturning(hostErr)
ctx := context.Background()
// Every db.* verb that crosses the transport must classify TX_EXPIRED.
t.Run("query", func(t *testing.T) {
_, err := tr.query(ctx, "SELECT 1", 7, nil)
if !errors.Is(err, ErrTxExpired) {
t.Fatalf("query err = %v, want ErrTxExpired", err)
}
})
t.Run("exec", func(t *testing.T) {
_, err := tr.exec(ctx, "DELETE FROM t", 7, nil)
if !errors.Is(err, ErrTxExpired) {
t.Fatalf("exec err = %v, want ErrTxExpired", err)
}
})
t.Run("txBegin", func(t *testing.T) {
_, err := tr.txBegin(ctx)
if !errors.Is(err, ErrTxExpired) {
t.Fatalf("txBegin err = %v, want ErrTxExpired", err)
}
})
t.Run("txCommit", func(t *testing.T) {
if err := tr.txCommit(ctx, 7); !errors.Is(err, ErrTxExpired) {
t.Fatalf("txCommit err = %v, want ErrTxExpired", err)
}
})
t.Run("txRollback", func(t *testing.T) {
if err := tr.txRollback(ctx, 7); !errors.Is(err, ErrTxExpired) {
t.Fatalf("txRollback err = %v, want ErrTxExpired", err)
}
})
}
func TestTransportLeavesOtherAbiErrorsUntouched(t *testing.T) {
hostErr := &fakeHostErr{code: abiv1.AbiErrorCode_ABI_ERROR_CODE_INTERNAL, msg: "boom"}
tr := transportReturning(hostErr)
_, err := tr.query(context.Background(), "SELECT 1", 0, nil)
if errors.Is(err, ErrTxExpired) {
t.Fatalf("non-tx-expired error was misclassified as ErrTxExpired: %v", err)
}
if !errors.Is(err, error(hostErr)) {
t.Fatalf("original host error not preserved: %v", err)
}
}