package historyreadback import ( "context" "crypto/ed25519" "encoding/json" "errors" "os" "os/exec" "path/filepath" "strings" "testing" "github.com/carlosframework/platform/internal/hostactors" "github.com/carlosframework/platform/internal/release" ) type crashStore struct { release.Store boundary string } func (s crashStore) Put(ctx context.Context, key string, raw []byte, tag string) (string, error) { if strings.HasSuffix(key, "/receipt.json") && s.boundary == "before" { os.Exit(75) } out, err := s.Store.Put(ctx, key, raw, tag) if strings.HasSuffix(key, "/receipt.json") && s.boundary == "after" && err == nil { os.Exit(75) } return out, err } func TestPlan14HistoryReadbackProcessCrash(t *testing.T) { if boundary := os.Getenv("PLAN14_READBACK_CRASH"); boundary != "" { dir := os.Getenv("PLAN14_READBACK_DIR") raw, err := os.ReadFile(filepath.Join(dir, "fixture.json")) if err != nil { t.Fatal(err) } var saved struct { Request Request Status release.HistoryMigrationStatus Root ed25519.PublicKey Key ed25519.PrivateKey } if err := json.Unmarshal(raw, &saved); err != nil { t.Fatal(err) } store := release.NewDirStore(filepath.Join(dir, "store")) d := hostactors.Directory{Store: crashStore{Store: store, boundary: boundary}, PublicKey: saved.Root, Bucket: saved.Request.Scope.Bucket} if err := Respond(t.Context(), d, saved.Request.Scope, saved.Key, saved.Request.IssuedAt, func(context.Context) (release.HistoryMigrationStatus, error) { return saved.Status, nil }); err != nil { t.Fatal(err) } t.Fatal("missed publication crash") } for _, boundary := range []string{"before", "after"} { t.Run(boundary, func(t *testing.T) { f := newFixture(t) dir := t.TempDir() target := release.NewDirStore(filepath.Join(dir, "store")) roster, _, err := f.directory.Store.Get(t.Context(), hostactors.Key) if err != nil { t.Fatal(err) } if _, err := target.Put(t.Context(), hostactors.Key, roster, ""); err != nil { t.Fatal(err) } f.directory.Store = target q, err := RequestStatus(t.Context(), f.directory, f.signer, f.scope, f.now) if err != nil { t.Fatal(err) } saved := struct { Request Request Status release.HistoryMigrationStatus Root ed25519.PublicKey Key ed25519.PrivateKey }{q, f.status, f.directory.PublicKey, f.key} raw, err := json.Marshal(saved) if err != nil { t.Fatal(err) } if err := os.WriteFile(filepath.Join(dir, "fixture.json"), raw, 0600); err != nil { t.Fatal(err) } cmd := exec.CommandContext(t.Context(), os.Args[0], "-test.run=^TestPlan14HistoryReadbackProcessCrash$", "-test.count=1") cmd.Dir = t.TempDir() cmd.Env = append(os.Environ(), "PLAN14_READBACK_CRASH="+boundary, "PLAN14_READBACK_DIR="+dir) out, err := cmd.CombinedOutput() var exit *exec.ExitError if !errors.As(err, &exit) || exit.ExitCode() != 75 { t.Fatalf("child: %v\n%s", err, out) } _, err = ReadStatus(t.Context(), f.directory, q, f.now) if boundary == "before" && !errors.Is(err, os.ErrNotExist) { t.Fatalf("crash before publication: %v", err) } if boundary == "after" && err != nil { t.Fatal(err) } if err := Respond(t.Context(), f.directory, f.scope, f.key, f.now, f.snapshot); err != nil { t.Fatal(err) } if _, err := ReadStatus(t.Context(), f.directory, q, f.now); err != nil { t.Fatal(err) } next, err := RequestStatus(t.Context(), f.directory, f.signer, f.scope, f.now) if err != nil { t.Fatal(err) } if _, err := ReadStatus(t.Context(), f.directory, next, f.now); err == nil { t.Fatal("precrash receipt completed a new challenge") } }) } }