feat(store): persist per-domain custom record marks
This commit is contained in:
@@ -277,3 +277,89 @@ func TestSetDomainTemplate_RejectsForeignProjectTemplate(t *testing.T) {
|
||||
t.Fatal("expected error binding a template from a different project, got nil")
|
||||
}
|
||||
}
|
||||
|
||||
// seedDomain creates a provider account and imports a single domain for
|
||||
// defaultProject, returning the resulting Domain — the shared setup used by
|
||||
// the custom-records tests below.
|
||||
func seedDomain(t *testing.T, s *Store, ctx context.Context) Domain {
|
||||
t.Helper()
|
||||
acc, err := s.Queries().CreateAccount(ctx, db.CreateAccountParams{
|
||||
ID: uuid.New(), ProjectID: defaultProject, Provider: "selectel", SecretEnc: "enc-blob",
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
doms, err := s.ImportDomains(ctx, defaultProject, acc.ID, []provider.Zone{{ID: "z1", Name: "a.example.com"}})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
return doms[0]
|
||||
}
|
||||
|
||||
// TestCustomRecordsAddIsIdempotentAndScoped verifies that AddCustomRecord
|
||||
// is idempotent (ON CONFLICT DO NOTHING — no PK-conflict error on repeat)
|
||||
// and that it is scoped by projectID, closing the same IDOR-on-write gap
|
||||
// SetDomainTemplate guards against.
|
||||
func TestCustomRecordsAddIsIdempotentAndScoped(t *testing.T) {
|
||||
s, ctx := newStore(t)
|
||||
dom := seedDomain(t, s, ctx)
|
||||
|
||||
const key = "CNAME admin.example.com."
|
||||
if err := s.AddCustomRecord(ctx, dom.ID, dom.ProjectID, key); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
// Повторное добавление того же ключа не должно падать на PK-конфликте.
|
||||
if err := s.AddCustomRecord(ctx, dom.ID, dom.ProjectID, key); err != nil {
|
||||
t.Fatalf("second add must be a no-op, got %v", err)
|
||||
}
|
||||
keys, err := s.ListCustomKeys(ctx, dom.ID)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if len(keys) != 1 || keys[0] != key {
|
||||
t.Fatalf("expected exactly [%q], got %+v", key, keys)
|
||||
}
|
||||
|
||||
// Чужой проект не может пометить этот домен.
|
||||
if err := s.AddCustomRecord(ctx, dom.ID, uuid.New(), "A www.example.com."); err == nil {
|
||||
t.Fatal("expected an error when marking a domain from another project")
|
||||
}
|
||||
}
|
||||
|
||||
// TestCustomRecordsDeleteAndCascade verifies DeleteCustomRecord removes the
|
||||
// mark, and that deleting the domain cascades to remove any remaining marks
|
||||
// (domain_custom_records.domain_id REFERENCES domains(id) ON DELETE CASCADE).
|
||||
func TestCustomRecordsDeleteAndCascade(t *testing.T) {
|
||||
s, ctx := newStore(t)
|
||||
dom := seedDomain(t, s, ctx)
|
||||
|
||||
const key = "CNAME admin.example.com."
|
||||
if err := s.AddCustomRecord(ctx, dom.ID, dom.ProjectID, key); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := s.DeleteCustomRecord(ctx, dom.ID, dom.ProjectID, key); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
keys, err := s.ListCustomKeys(ctx, dom.ID)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if len(keys) != 0 {
|
||||
t.Fatalf("expected no keys after delete, got %+v", keys)
|
||||
}
|
||||
|
||||
// Каскад: удаление домена уносит его пометки.
|
||||
if err := s.AddCustomRecord(ctx, dom.ID, dom.ProjectID, key); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := s.DeleteDomain(ctx, dom.ID, dom.ProjectID); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
keys, err = s.ListCustomKeys(ctx, dom.ID)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if len(keys) != 0 {
|
||||
t.Fatalf("expected cascade to remove keys with the domain, got %+v", keys)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user