mirror of
https://github.com/tiennm99/goclaw.git
synced 2026-10-12 18:16:09 +00:00
* fix(backup): harden tenant restore preview and lookup handling * test(pg): fix hook test migration path * test(ci): stabilize race coverage tests * fix(hooks): scope GetByID and allow loopback tests * fix(backup): harden tenant restore replace/new contracts + race-safe SSRF flag - Replace mode no longer deletes the tenants row (FK safe vs excluded diagnostic tables: traces, activity_logs, usage_snapshots, spans, embedding_cache, pairing_requests, paired_devices, channel_pending_messages, cron_run_logs). Metadata is preserved in place. - shouldRestoreTable now excludes tenants for both new and replace modes. - CLI: add validateTenantRestoreFlags guardrail. mode=new requires --new-tenant-slug and rejects --tenant/--tenant-id; upsert/replace warn on stray --new-tenant-slug; invalid --mode values rejected. TAB in help text fixed; flag descriptions clarified. - HTTP: resolveRestoreTarget rejects tenant_id for mode=new regardless of tenant_slug (matches CLI contract). New i18n key MsgRestoreNewModeRejectsTenantID (en/vi/zh). - security/ssrf: allowLoopbackForTest switched to atomic.Bool so concurrent reads from outbound dialers are race-safe. - Polish: vi backup.json key order matches en/zh; TenantRestoreOptions.Mode doc comment documents upsert/replace/new semantics including clone behavior for new. - Tests: unit coverage for validator (12 cases), HTTP guardrails (3 cases), shouldRestoreTable replace branch. Integration test tests/integration/tenant_restore_replace_test.go regression-guards the FK fix using activity_logs seed + DeleteTenantDataForTest helper. --------- Co-authored-by: Viet Tran <viettranx@gmail.com>
43 lines
1.4 KiB
Go
43 lines
1.4 KiB
Go
package cmd
|
|
|
|
import (
|
|
"strings"
|
|
"testing"
|
|
)
|
|
|
|
func TestValidateTenantRestoreFlags(t *testing.T) {
|
|
tests := []struct {
|
|
name, mode, tenant, tenantID, newSlug, wantErr string
|
|
}{
|
|
{"new ok", "new", "", "", "fresh-slug", ""},
|
|
{"new missing slug", "new", "", "", "", "required for mode=new"},
|
|
{"new with tenant slug", "new", "acme", "", "fresh-slug", "does not accept"},
|
|
{"new with tenant-id", "new", "", "uuid-x", "fresh-slug", "does not accept"},
|
|
{"upsert ok tenant", "upsert", "acme", "", "", ""},
|
|
{"upsert ok tenant-id", "upsert", "", "uuid-x", "", ""},
|
|
{"upsert ok both", "upsert", "acme", "uuid-x", "", ""},
|
|
{"upsert missing", "upsert", "", "", "", "required for mode=upsert"},
|
|
{"upsert ignores new slug (no error)", "upsert", "acme", "", "ignored", ""},
|
|
{"replace ok", "replace", "acme", "", "", ""},
|
|
{"replace missing", "replace", "", "", "", "required for mode=replace"},
|
|
{"invalid mode", "bogus", "acme", "", "", "invalid --mode"},
|
|
}
|
|
for _, tc := range tests {
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
err := validateTenantRestoreFlags(tc.mode, tc.tenant, tc.tenantID, tc.newSlug)
|
|
if tc.wantErr == "" {
|
|
if err != nil {
|
|
t.Fatalf("want nil, got %v", err)
|
|
}
|
|
return
|
|
}
|
|
if err == nil {
|
|
t.Fatalf("want err containing %q, got nil", tc.wantErr)
|
|
}
|
|
if !strings.Contains(err.Error(), tc.wantErr) {
|
|
t.Fatalf("want err containing %q, got %q", tc.wantErr, err.Error())
|
|
}
|
|
})
|
|
}
|
|
}
|