58 lines
2.2 KiB
Go
58 lines
2.2 KiB
Go
package pipeline
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"testing"
|
|
|
|
"textmachine/backend/internal/store"
|
|
)
|
|
|
|
// refusal_test.go pins RefuseStoreOpen, the one place a store-open failure becomes a shell class. Two
|
|
// callers share it (openRunner and `tmctl migrate`), and its arms decide what an automated caller DOES:
|
|
// wait, migrate, or treat the project as broken.
|
|
|
|
func TestRefuseStoreOpenClassifiesWhatACallerActsOn(t *testing.T) {
|
|
locked := fmt.Errorf("opening the project: %w", store.ErrLocked)
|
|
mismatch := fmt.Errorf("opening the project: %w", &store.SchemaMismatchError{Path: "/b.db", Found: 14, Expected: 15})
|
|
|
|
for _, tc := range []struct {
|
|
name string
|
|
err error
|
|
want RefusalClass
|
|
}{
|
|
{"another process holds it", locked, RefusalProjectLocked},
|
|
{"the schema is not this binary's", mismatch, RefusalSchemaMismatch},
|
|
} {
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
var refusal *Refusal
|
|
if !errors.As(RefuseStoreOpen(tc.err), &refusal) {
|
|
t.Fatalf("%v was not classified as a refusal", tc.err)
|
|
}
|
|
if refusal.Class != tc.want {
|
|
t.Fatalf("class %q, want %q", refusal.Class, tc.want)
|
|
}
|
|
// Both arms must survive a %w-wrap, because that is how they arrive: neither caller opens the
|
|
// store at the top of its own stack.
|
|
if !errors.Is(refusal, store.ErrLocked) && !errors.As(refusal, new(*store.SchemaMismatchError)) {
|
|
t.Fatal("the classification dropped the cause it was made from")
|
|
}
|
|
})
|
|
}
|
|
|
|
// The safety rule, and the reason the default arm is a passthrough: the refusal band means "turned
|
|
// down before doing ANY work", and the platform's intake acts on it — it waits instead of failing,
|
|
// and one number in it deletes the user's upload. A disk error during an open is not that; it must
|
|
// stay exit 1 (infra failure) rather than borrow a class that promises nothing happened.
|
|
disk := errors.New("store: open write pool: input/output error")
|
|
got := RefuseStoreOpen(disk)
|
|
if !errors.Is(got, disk) {
|
|
t.Fatalf("an unrecognised open failure must come back with its cause intact, got %v", got)
|
|
}
|
|
if errors.As(got, new(*Refusal)) {
|
|
t.Fatal("an unrecognised open failure was swept into the refusal band")
|
|
}
|
|
if RefuseStoreOpen(nil) != nil {
|
|
t.Fatal("a nil failure must stay nil: the classifier never invents one")
|
|
}
|
|
}
|