package mock import ( "context" "errors" "os" "path/filepath" "strings" "tera-clone/services/tera-api/internal/domain" "tera-clone/services/tera-api/internal/supportbundle" "tera-clone/services/tera-api/internal/tlscert" "testing" ) type resetRebooter struct { calls int check func() } func (r *resetRebooter) Reboot(context.Context) error { if r.check != nil { r.check() } r.calls++ return nil } func TestFactoryResetErasesPersistedDataAndRestoresOnlyTerauser(t *testing.T) { root := t.TempDir() certOptions := tlscert.Options{Root: filepath.Join(root, "tls"), Clock: fixedClock} certs, err := tlscert.Open(certOptions) if err != nil { t.Fatal(err) } bundles, err := supportbundle.NewManager(supportbundle.Options{Directory: filepath.Join(root, "bundles")}) if err != nil { t.Fatal(err) } reboot := &resetRebooter{} options := StoreOptions{ConfigPath: filepath.Join(root, "config.json"), AuthPath: filepath.Join(root, "auth.json"), SessionPath: filepath.Join(root, "sessions.json"), LoginFailurePath: filepath.Join(root, "failures.json"), SecretPath: filepath.Join(root, "secrets.json"), OperationPath: filepath.Join(root, "operations.json"), AuditPath: filepath.Join(root, "audit.jsonl"), SystemLogPath: filepath.Join(root, "system.jsonl"), PortLogPath: filepath.Join(root, "port.jsonl"), LinkLogPath: filepath.Join(root, "link.jsonl"), TLSCertificateStore: certs, SupportBundleService: bundles, SystemController: reboot} store, err := NewStoreWithOptions(fixedClock, options) if err != nil { t.Fatal(err) } defer store.Close() oldSession, err := store.Login(context.Background(), domain.AuthLoginRequest{Username: "admin", Password: "mock-admin-secret"}) if err != nil { t.Fatal(err) } if err := store.secrets.Set("customer/secret", []byte("customer-secret-marker")); err != nil { t.Fatal(err) } store.systemLogEvents = []domain.SystemLogEvent{{Message: "old-system-marker"}} store.portLogEvents = []domain.PortLogEvent{{Message: "old-port-marker"}} store.linkLogEvents = []domain.LinkLogEvent{{Message: "old-link-marker"}} store.authGroupOverrides["customer"] = authGroupRecord{Name: "Customer", Role: "port_operator"} store.addressFilters = domain.AddressFilterSettings{Enabled: true, Mode: "whitelist"} store.loginFailures["customer"] = loginFailureRecord{} if _, err := certs.Generate("customer", tlscert.Subject{CommonName: "customer.example"}); err != nil { t.Fatal(err) } if _, err := bundles.Create(context.Background(), supportbundle.CreateRequest{BundleID: "support_bundle_20260908_000001", GeneratedAt: fixedClock(), Sections: []supportbundle.Section{{Name: "config.json", Value: "old-bundle-marker"}}}); err != nil { t.Fatal(err) } store.runningManagement.CaptchaEnabled = false store.persistentManagement.CaptchaEnabled = false reboot.check = func() { content, err := os.ReadFile(options.AuthPath) if err != nil { t.Fatal(err) } if strings.Contains(string(content), `"admin"`) || !strings.Contains(string(content), `"terauser"`) { t.Fatal("reboot requested before account reset was persisted") } } if _, err := store.FactoryReset(context.Background()); err != nil { t.Fatal(err) } if !store.runningManagement.CaptchaEnabled || !store.persistentManagement.CaptchaEnabled { t.Fatal("factory reset disabled CAPTCHA") } if reboot.calls != 1 { t.Fatal("reset did not request reboot") } if _, err := store.ActorBySession(context.Background(), oldSession.SessionToken); err == nil { t.Fatal("old session survived") } if len(store.authUsers) != 1 || len(store.authGroupOverrides) != 0 || len(store.authTokens) != 0 || len(store.loginFailures) != 0 || store.addressFilters.Enabled { t.Fatal("security state survived reset") } if store.secrets.Has("customer/secret") { t.Fatal("stored credential survived") } if len(certs.List("factory-default")) != 1 { t.Fatal("customer certificate survived") } if _, err := bundles.Read(context.Background(), "support_bundle_20260908_000001"); err == nil { t.Fatal("support bundle survived") } for _, path := range []string{options.SystemLogPath, options.PortLogPath, options.LinkLogPath, options.AuditPath} { content, err := os.ReadFile(path) if err != nil { t.Fatal(err) } if strings.Contains(string(content), "old-") { t.Fatalf("old log survived in %s", path) } } options.RemoveLegacyAuthUsers = true options.TLSCertificateStore, err = tlscert.Open(certOptions) if err != nil { t.Fatal(err) } reopened, err := NewStoreWithOptions(fixedClock, options) if err != nil { t.Fatal(err) } defer reopened.Close() if !reopened.runningManagement.CaptchaEnabled { t.Fatal("CAPTCHA disabled after reboot") } if len(reopened.authUsers) != 1 { t.Fatalf("bootstrap accounts resurrected: %d", len(reopened.authUsers)) } if _, err := reopened.Login(context.Background(), domain.AuthLoginRequest{Username: "terauser", Password: "terauser"}); err != nil { t.Fatal(err) } if _, err := reopened.Login(context.Background(), domain.AuthLoginRequest{Username: "admin", Password: "mock-admin-secret"}); err == nil { t.Fatal("old account works after restart") } } func TestFactoryResetCleanupFailureDoesNotRebootOrClearAccounts(t *testing.T) { reboot := &resetRebooter{} store, err := NewStoreWithOptions(fixedClock, StoreOptions{SystemController: reboot, FactoryResetCleanup: func() error { return errors.New("cleanup failed") }}) if err != nil { t.Fatal(err) } defer store.Close() if _, err := store.FactoryReset(context.Background()); err == nil { t.Fatal("reset unexpectedly succeeded") } if reboot.calls != 0 || len(store.authUsers) != 3 { t.Fatal("failed cleanup reported success or changed accounts") } } func TestFactoryResetBlocksFurtherMutations(t *testing.T) { s := NewStore(fixedClock) defer s.Close() release, ok := s.BeginMutation() if !ok { t.Fatal("new store rejected writes") } release() if _, err := s.FactoryReset(context.Background()); err != nil { t.Fatal(err) } release, ok = s.BeginMutation() defer release() if ok { t.Fatal("writes allowed after reset before reboot") } }