Files
felhom.eu/hub/internal/offsite/r32_purge_test.go
T

71 lines
3.1 KiB
Go

package offsite
import (
"context"
"errors"
"testing"
"gitea.dooplex.hu/admin/felhom-hub/internal/hetznerapi"
)
// orderAPI records when the sub-account is deleted relative to the purge.
type orderAPI struct {
*hetznerapi.Fake
log *[]string
}
func (o *orderAPI) DeleteSubaccount(ctx context.Context, boxID, subID int64) (hetznerapi.Action, error) {
*o.log = append(*o.log, "delete-subaccount")
return o.Fake.DeleteSubaccount(ctx, boxID, subID)
}
// R-32 option A (`09` §3 decision 167) — RESET's shared-tier teardown purges the folder through the sub-account's
// own login BEFORE the sub-account is deleted; a sub-account is a login, its folder survives its deletion.
//
// COMPANION RED-PROOF (observed): remove the PurgeShared call from Deprovision → this fails with
// "the folder must be purged before the sub-account is deleted; order [delete-subaccount]". Restored.
func TestDeprovision_R32_PurgesFolderBeforeDeletingSubaccount(t *testing.T) {
p, fake, _ := newTestProvisioner(t)
if _, err := p.ProvisionOffsite(context.Background(), "cust-r32", Input{Enabled: true, Type: "shared", QuotaGB: 10}); err != nil {
t.Fatal(err)
}
var order []string
p.API = &orderAPI{Fake: fake, log: &order}
p.PurgeShared = func(_ context.Context, id string) error { order = append(order, "purge:"+id); return nil }
if err := p.Deprovision(context.Background(), "cust-r32", "shared"); err != nil {
t.Fatal(err)
}
if len(order) != 2 || order[0] != "purge:cust-r32" || order[1] != "delete-subaccount" {
t.Fatalf("the folder must be purged before the sub-account is deleted; order %v", order)
}
}
// A failed purge keeps the sub-account (after its deletion only the main account reaches the folder), and the
// error reaches RESET (its hetzner leg reads "failed" and a re-run resumes).
// COMPANION RED-PROOF (observed): ignore PurgeShared's error → this fails with "a failed purge must keep the
// sub-account; deleted=1". Restored.
func TestDeprovision_R32_FailedPurgeKeepsSubaccount(t *testing.T) {
p, fake, _ := newTestProvisioner(t)
if _, err := p.ProvisionOffsite(context.Background(), "cust-r32b", Input{Enabled: true, Type: "shared", QuotaGB: 10}); err != nil {
t.Fatal(err)
}
p.PurgeShared = func(context.Context, string) error { return errors.New("permission denied") }
if err := p.Deprovision(context.Background(), "cust-r32b", "shared"); err == nil {
t.Fatal("a failed purge must fail Deprovision")
}
if fake.DeletedSubaccounts != 0 {
t.Fatalf("a failed purge must keep the sub-account; deleted=%d", fake.DeletedSubaccounts)
}
}
// No purge route wired → refuse; never a silent delete that strands the folder.
func TestDeprovision_R32_NoPurgeRouteRefuses(t *testing.T) {
p, fake, _ := newTestProvisioner(t)
if _, err := p.ProvisionOffsite(context.Background(), "cust-r32c", Input{Enabled: true, Type: "shared", QuotaGB: 10}); err != nil {
t.Fatal(err)
}
if err := p.Deprovision(context.Background(), "cust-r32c", "shared"); err == nil || fake.DeletedSubaccounts != 0 {
t.Fatalf("no purge route must refuse and keep the sub-account; err=%v deleted=%d", err, fake.DeletedSubaccounts)
}
}