Phase 5 (OCI pull): internal/oci/ package with manifest GET/HEAD by tag/digest, blob GET/HEAD with repo membership check, tag listing with OCI pagination, catalog listing. Multi-segment repo names via parseOCIPath() right-split routing. DB query layer in internal/db/repository.go. Phase 6 (OCI push): blob uploads (monolithic and chunked) with uploadManager tracking in-progress BlobWriters, manifest push implementing full ARCHITECTURE.md §5 flow in a single SQLite transaction (create repo, upsert manifest, populate manifest_blobs, atomic tag move). Digest verification on both blob commit and manifest push-by-digest. Phase 8 (admin REST): /v1 endpoints for auth (login/logout/health), repository management (list/detail/delete), policy CRUD with engine reload, audit log listing with filters, GC trigger/status stubs. RequireAdmin middleware, platform-standard error format. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
594 lines
15 KiB
Go
594 lines
15 KiB
Go
package db
|
|
|
|
import (
|
|
"errors"
|
|
"testing"
|
|
)
|
|
|
|
// seedAdminRepo inserts a repository with manifests, tags, and blobs for admin tests.
|
|
func seedAdminRepo(t *testing.T, d *DB, name string, tagNames []string) int64 {
|
|
t.Helper()
|
|
|
|
_, err := d.Exec(`INSERT INTO repositories (name) VALUES (?)`, name)
|
|
if err != nil {
|
|
t.Fatalf("insert repo %q: %v", name, err)
|
|
}
|
|
|
|
var repoID int64
|
|
if err := d.QueryRow(`SELECT id FROM repositories WHERE name = ?`, name).Scan(&repoID); err != nil {
|
|
t.Fatalf("select repo id: %v", err)
|
|
}
|
|
|
|
_, err = d.Exec(
|
|
`INSERT INTO manifests (repository_id, digest, media_type, content, size)
|
|
VALUES (?, ?, 'application/vnd.oci.image.manifest.v1+json', '{}', 1024)`,
|
|
repoID, "sha256:aaa-"+name,
|
|
)
|
|
if err != nil {
|
|
t.Fatalf("insert manifest for %q: %v", name, err)
|
|
}
|
|
|
|
var manifestID int64
|
|
if err := d.QueryRow(`SELECT id FROM manifests WHERE repository_id = ?`, repoID).Scan(&manifestID); err != nil {
|
|
t.Fatalf("select manifest id: %v", err)
|
|
}
|
|
|
|
for _, tag := range tagNames {
|
|
_, err := d.Exec(`INSERT INTO tags (repository_id, name, manifest_id) VALUES (?, ?, ?)`,
|
|
repoID, tag, manifestID)
|
|
if err != nil {
|
|
t.Fatalf("insert tag %q: %v", tag, err)
|
|
}
|
|
}
|
|
|
|
return repoID
|
|
}
|
|
|
|
func TestListRepositoriesWithMetadata(t *testing.T) {
|
|
d := migratedTestDB(t)
|
|
|
|
seedAdminRepo(t, d, "alpha/app", []string{"latest", "v1.0"})
|
|
seedAdminRepo(t, d, "bravo/lib", []string{"latest"})
|
|
|
|
repos, err := d.ListRepositoriesWithMetadata(50, 0)
|
|
if err != nil {
|
|
t.Fatalf("ListRepositoriesWithMetadata: %v", err)
|
|
}
|
|
if len(repos) != 2 {
|
|
t.Fatalf("repo count: got %d, want 2", len(repos))
|
|
}
|
|
|
|
// Ordered by name ASC.
|
|
if repos[0].Name != "alpha/app" {
|
|
t.Fatalf("first repo name: got %q, want %q", repos[0].Name, "alpha/app")
|
|
}
|
|
if repos[0].TagCount != 2 {
|
|
t.Fatalf("alpha/app tag count: got %d, want 2", repos[0].TagCount)
|
|
}
|
|
if repos[0].ManifestCount != 1 {
|
|
t.Fatalf("alpha/app manifest count: got %d, want 1", repos[0].ManifestCount)
|
|
}
|
|
if repos[0].TotalSize != 1024 {
|
|
t.Fatalf("alpha/app total size: got %d, want 1024", repos[0].TotalSize)
|
|
}
|
|
if repos[0].CreatedAt == "" {
|
|
t.Fatal("alpha/app created_at: expected non-empty")
|
|
}
|
|
|
|
if repos[1].Name != "bravo/lib" {
|
|
t.Fatalf("second repo name: got %q, want %q", repos[1].Name, "bravo/lib")
|
|
}
|
|
if repos[1].TagCount != 1 {
|
|
t.Fatalf("bravo/lib tag count: got %d, want 1", repos[1].TagCount)
|
|
}
|
|
}
|
|
|
|
func TestListRepositoriesWithMetadataEmpty(t *testing.T) {
|
|
d := migratedTestDB(t)
|
|
|
|
repos, err := d.ListRepositoriesWithMetadata(50, 0)
|
|
if err != nil {
|
|
t.Fatalf("ListRepositoriesWithMetadata: %v", err)
|
|
}
|
|
if repos != nil {
|
|
t.Fatalf("expected nil repos, got %v", repos)
|
|
}
|
|
}
|
|
|
|
func TestListRepositoriesWithMetadataPagination(t *testing.T) {
|
|
d := migratedTestDB(t)
|
|
|
|
seedAdminRepo(t, d, "alpha/app", []string{"latest"})
|
|
seedAdminRepo(t, d, "bravo/lib", []string{"latest"})
|
|
seedAdminRepo(t, d, "charlie/svc", []string{"latest"})
|
|
|
|
repos, err := d.ListRepositoriesWithMetadata(2, 0)
|
|
if err != nil {
|
|
t.Fatalf("ListRepositoriesWithMetadata page 1: %v", err)
|
|
}
|
|
if len(repos) != 2 {
|
|
t.Fatalf("page 1 count: got %d, want 2", len(repos))
|
|
}
|
|
if repos[0].Name != "alpha/app" {
|
|
t.Fatalf("page 1 first: got %q, want %q", repos[0].Name, "alpha/app")
|
|
}
|
|
|
|
repos, err = d.ListRepositoriesWithMetadata(2, 2)
|
|
if err != nil {
|
|
t.Fatalf("ListRepositoriesWithMetadata page 2: %v", err)
|
|
}
|
|
if len(repos) != 1 {
|
|
t.Fatalf("page 2 count: got %d, want 1", len(repos))
|
|
}
|
|
if repos[0].Name != "charlie/svc" {
|
|
t.Fatalf("page 2 first: got %q, want %q", repos[0].Name, "charlie/svc")
|
|
}
|
|
}
|
|
|
|
func TestGetRepositoryDetail(t *testing.T) {
|
|
d := migratedTestDB(t)
|
|
|
|
seedAdminRepo(t, d, "myorg/myapp", []string{"latest", "v1.0"})
|
|
|
|
detail, err := d.GetRepositoryDetail("myorg/myapp")
|
|
if err != nil {
|
|
t.Fatalf("GetRepositoryDetail: %v", err)
|
|
}
|
|
if detail.Name != "myorg/myapp" {
|
|
t.Fatalf("name: got %q, want %q", detail.Name, "myorg/myapp")
|
|
}
|
|
if detail.CreatedAt == "" {
|
|
t.Fatal("created_at: expected non-empty")
|
|
}
|
|
if len(detail.Tags) != 2 {
|
|
t.Fatalf("tag count: got %d, want 2", len(detail.Tags))
|
|
}
|
|
// Tags ordered by name ASC.
|
|
if detail.Tags[0].Name != "latest" {
|
|
t.Fatalf("first tag: got %q, want %q", detail.Tags[0].Name, "latest")
|
|
}
|
|
if detail.Tags[0].Digest == "" {
|
|
t.Fatal("first tag digest: expected non-empty")
|
|
}
|
|
if detail.Tags[1].Name != "v1.0" {
|
|
t.Fatalf("second tag: got %q, want %q", detail.Tags[1].Name, "v1.0")
|
|
}
|
|
|
|
if len(detail.Manifests) != 1 {
|
|
t.Fatalf("manifest count: got %d, want 1", len(detail.Manifests))
|
|
}
|
|
if detail.Manifests[0].Size != 1024 {
|
|
t.Fatalf("manifest size: got %d, want 1024", detail.Manifests[0].Size)
|
|
}
|
|
if detail.TotalSize != 1024 {
|
|
t.Fatalf("total size: got %d, want 1024", detail.TotalSize)
|
|
}
|
|
}
|
|
|
|
func TestGetRepositoryDetailNotFound(t *testing.T) {
|
|
d := migratedTestDB(t)
|
|
|
|
_, err := d.GetRepositoryDetail("nonexistent/repo")
|
|
if !errors.Is(err, ErrRepoNotFound) {
|
|
t.Fatalf("expected ErrRepoNotFound, got %v", err)
|
|
}
|
|
}
|
|
|
|
func TestGetRepositoryDetailEmptyRepo(t *testing.T) {
|
|
d := migratedTestDB(t)
|
|
|
|
_, err := d.Exec(`INSERT INTO repositories (name) VALUES ('empty/repo')`)
|
|
if err != nil {
|
|
t.Fatalf("insert repo: %v", err)
|
|
}
|
|
|
|
detail, err := d.GetRepositoryDetail("empty/repo")
|
|
if err != nil {
|
|
t.Fatalf("GetRepositoryDetail: %v", err)
|
|
}
|
|
if len(detail.Tags) != 0 {
|
|
t.Fatalf("tags: got %d, want 0", len(detail.Tags))
|
|
}
|
|
if len(detail.Manifests) != 0 {
|
|
t.Fatalf("manifests: got %d, want 0", len(detail.Manifests))
|
|
}
|
|
if detail.TotalSize != 0 {
|
|
t.Fatalf("total size: got %d, want 0", detail.TotalSize)
|
|
}
|
|
}
|
|
|
|
func TestDeleteRepository(t *testing.T) {
|
|
d := migratedTestDB(t)
|
|
|
|
seedAdminRepo(t, d, "myorg/myapp", []string{"latest"})
|
|
|
|
if err := d.DeleteRepository("myorg/myapp"); err != nil {
|
|
t.Fatalf("DeleteRepository: %v", err)
|
|
}
|
|
|
|
// Verify it's gone.
|
|
_, err := d.GetRepositoryDetail("myorg/myapp")
|
|
if !errors.Is(err, ErrRepoNotFound) {
|
|
t.Fatalf("expected ErrRepoNotFound after delete, got %v", err)
|
|
}
|
|
|
|
// Verify cascade: manifests and tags should be gone.
|
|
var manifestCount int
|
|
if err := d.QueryRow(`SELECT COUNT(*) FROM manifests`).Scan(&manifestCount); err != nil {
|
|
t.Fatalf("count manifests: %v", err)
|
|
}
|
|
if manifestCount != 0 {
|
|
t.Fatalf("manifests after delete: got %d, want 0", manifestCount)
|
|
}
|
|
|
|
var tagCount int
|
|
if err := d.QueryRow(`SELECT COUNT(*) FROM tags`).Scan(&tagCount); err != nil {
|
|
t.Fatalf("count tags: %v", err)
|
|
}
|
|
if tagCount != 0 {
|
|
t.Fatalf("tags after delete: got %d, want 0", tagCount)
|
|
}
|
|
}
|
|
|
|
func TestDeleteRepositoryNotFound(t *testing.T) {
|
|
d := migratedTestDB(t)
|
|
|
|
err := d.DeleteRepository("nonexistent/repo")
|
|
if !errors.Is(err, ErrRepoNotFound) {
|
|
t.Fatalf("expected ErrRepoNotFound, got %v", err)
|
|
}
|
|
}
|
|
|
|
func TestCreatePolicyRule(t *testing.T) {
|
|
d := migratedTestDB(t)
|
|
|
|
rule := PolicyRuleRow{
|
|
Priority: 50,
|
|
Description: "allow CI push",
|
|
Effect: "allow",
|
|
Roles: []string{"ci"},
|
|
Actions: []string{"registry:push", "registry:pull"},
|
|
Repositories: []string{"production/*"},
|
|
Enabled: true,
|
|
CreatedBy: "admin-uuid",
|
|
}
|
|
|
|
id, err := d.CreatePolicyRule(rule)
|
|
if err != nil {
|
|
t.Fatalf("CreatePolicyRule: %v", err)
|
|
}
|
|
if id == 0 {
|
|
t.Fatal("expected non-zero ID")
|
|
}
|
|
|
|
got, err := d.GetPolicyRule(id)
|
|
if err != nil {
|
|
t.Fatalf("GetPolicyRule: %v", err)
|
|
}
|
|
if got.Priority != 50 {
|
|
t.Fatalf("priority: got %d, want 50", got.Priority)
|
|
}
|
|
if got.Description != "allow CI push" {
|
|
t.Fatalf("description: got %q, want %q", got.Description, "allow CI push")
|
|
}
|
|
if got.Effect != "allow" {
|
|
t.Fatalf("effect: got %q, want %q", got.Effect, "allow")
|
|
}
|
|
if len(got.Roles) != 1 || got.Roles[0] != "ci" {
|
|
t.Fatalf("roles: got %v, want [ci]", got.Roles)
|
|
}
|
|
if len(got.Actions) != 2 {
|
|
t.Fatalf("actions: got %d, want 2", len(got.Actions))
|
|
}
|
|
if len(got.Repositories) != 1 || got.Repositories[0] != "production/*" {
|
|
t.Fatalf("repositories: got %v, want [production/*]", got.Repositories)
|
|
}
|
|
if !got.Enabled {
|
|
t.Fatal("enabled: got false, want true")
|
|
}
|
|
if got.CreatedBy != "admin-uuid" {
|
|
t.Fatalf("created_by: got %q, want %q", got.CreatedBy, "admin-uuid")
|
|
}
|
|
if got.CreatedAt == "" {
|
|
t.Fatal("created_at: expected non-empty")
|
|
}
|
|
if got.UpdatedAt == "" {
|
|
t.Fatal("updated_at: expected non-empty")
|
|
}
|
|
}
|
|
|
|
func TestCreatePolicyRuleDisabled(t *testing.T) {
|
|
d := migratedTestDB(t)
|
|
|
|
rule := PolicyRuleRow{
|
|
Priority: 10,
|
|
Description: "disabled rule",
|
|
Effect: "deny",
|
|
Actions: []string{"registry:delete"},
|
|
Enabled: false,
|
|
}
|
|
|
|
id, err := d.CreatePolicyRule(rule)
|
|
if err != nil {
|
|
t.Fatalf("CreatePolicyRule: %v", err)
|
|
}
|
|
|
|
got, err := d.GetPolicyRule(id)
|
|
if err != nil {
|
|
t.Fatalf("GetPolicyRule: %v", err)
|
|
}
|
|
if got.Enabled {
|
|
t.Fatal("enabled: got true, want false")
|
|
}
|
|
}
|
|
|
|
func TestGetPolicyRuleNotFound(t *testing.T) {
|
|
d := migratedTestDB(t)
|
|
|
|
_, err := d.GetPolicyRule(9999)
|
|
if !errors.Is(err, ErrPolicyRuleNotFound) {
|
|
t.Fatalf("expected ErrPolicyRuleNotFound, got %v", err)
|
|
}
|
|
}
|
|
|
|
func TestListPolicyRules(t *testing.T) {
|
|
d := migratedTestDB(t)
|
|
|
|
// Insert rules with different priorities (out of order).
|
|
rule1 := PolicyRuleRow{
|
|
Priority: 50,
|
|
Description: "rule A",
|
|
Effect: "allow",
|
|
Actions: []string{"registry:pull"},
|
|
Enabled: true,
|
|
}
|
|
rule2 := PolicyRuleRow{
|
|
Priority: 10,
|
|
Description: "rule B",
|
|
Effect: "deny",
|
|
Actions: []string{"registry:delete"},
|
|
Enabled: true,
|
|
}
|
|
rule3 := PolicyRuleRow{
|
|
Priority: 30,
|
|
Description: "rule C",
|
|
Effect: "allow",
|
|
Actions: []string{"registry:push"},
|
|
Enabled: false,
|
|
}
|
|
|
|
if _, err := d.CreatePolicyRule(rule1); err != nil {
|
|
t.Fatalf("CreatePolicyRule 1: %v", err)
|
|
}
|
|
if _, err := d.CreatePolicyRule(rule2); err != nil {
|
|
t.Fatalf("CreatePolicyRule 2: %v", err)
|
|
}
|
|
if _, err := d.CreatePolicyRule(rule3); err != nil {
|
|
t.Fatalf("CreatePolicyRule 3: %v", err)
|
|
}
|
|
|
|
rules, err := d.ListPolicyRules(50, 0)
|
|
if err != nil {
|
|
t.Fatalf("ListPolicyRules: %v", err)
|
|
}
|
|
if len(rules) != 3 {
|
|
t.Fatalf("rule count: got %d, want 3", len(rules))
|
|
}
|
|
|
|
// Should be ordered by priority ASC: 10, 30, 50.
|
|
if rules[0].Priority != 10 {
|
|
t.Fatalf("first rule priority: got %d, want 10", rules[0].Priority)
|
|
}
|
|
if rules[0].Description != "rule B" {
|
|
t.Fatalf("first rule description: got %q, want %q", rules[0].Description, "rule B")
|
|
}
|
|
if rules[1].Priority != 30 {
|
|
t.Fatalf("second rule priority: got %d, want 30", rules[1].Priority)
|
|
}
|
|
if rules[2].Priority != 50 {
|
|
t.Fatalf("third rule priority: got %d, want 50", rules[2].Priority)
|
|
}
|
|
|
|
// Verify enabled flags.
|
|
if !rules[0].Enabled {
|
|
t.Fatal("rule B enabled: got false, want true")
|
|
}
|
|
if rules[1].Enabled {
|
|
t.Fatal("rule C enabled: got true, want false")
|
|
}
|
|
}
|
|
|
|
func TestListPolicyRulesEmpty(t *testing.T) {
|
|
d := migratedTestDB(t)
|
|
|
|
rules, err := d.ListPolicyRules(50, 0)
|
|
if err != nil {
|
|
t.Fatalf("ListPolicyRules: %v", err)
|
|
}
|
|
if rules != nil {
|
|
t.Fatalf("expected nil rules, got %v", rules)
|
|
}
|
|
}
|
|
|
|
func TestUpdatePolicyRule(t *testing.T) {
|
|
d := migratedTestDB(t)
|
|
|
|
rule := PolicyRuleRow{
|
|
Priority: 50,
|
|
Description: "original",
|
|
Effect: "allow",
|
|
Actions: []string{"registry:pull"},
|
|
Enabled: true,
|
|
}
|
|
|
|
id, err := d.CreatePolicyRule(rule)
|
|
if err != nil {
|
|
t.Fatalf("CreatePolicyRule: %v", err)
|
|
}
|
|
|
|
// Update priority and description.
|
|
updates := PolicyRuleRow{
|
|
Priority: 25,
|
|
Description: "updated",
|
|
}
|
|
if err := d.UpdatePolicyRule(id, updates); err != nil {
|
|
t.Fatalf("UpdatePolicyRule: %v", err)
|
|
}
|
|
|
|
got, err := d.GetPolicyRule(id)
|
|
if err != nil {
|
|
t.Fatalf("GetPolicyRule: %v", err)
|
|
}
|
|
if got.Priority != 25 {
|
|
t.Fatalf("priority: got %d, want 25", got.Priority)
|
|
}
|
|
if got.Description != "updated" {
|
|
t.Fatalf("description: got %q, want %q", got.Description, "updated")
|
|
}
|
|
// Effect should be unchanged.
|
|
if got.Effect != "allow" {
|
|
t.Fatalf("effect: got %q, want %q (unchanged)", got.Effect, "allow")
|
|
}
|
|
// Actions should be unchanged.
|
|
if len(got.Actions) != 1 || got.Actions[0] != "registry:pull" {
|
|
t.Fatalf("actions: got %v, want [registry:pull] (unchanged)", got.Actions)
|
|
}
|
|
}
|
|
|
|
func TestUpdatePolicyRuleBody(t *testing.T) {
|
|
d := migratedTestDB(t)
|
|
|
|
rule := PolicyRuleRow{
|
|
Priority: 50,
|
|
Description: "test",
|
|
Effect: "allow",
|
|
Actions: []string{"registry:pull"},
|
|
Enabled: true,
|
|
}
|
|
|
|
id, err := d.CreatePolicyRule(rule)
|
|
if err != nil {
|
|
t.Fatalf("CreatePolicyRule: %v", err)
|
|
}
|
|
|
|
// Update rule body fields.
|
|
updates := PolicyRuleRow{
|
|
Effect: "deny",
|
|
Actions: []string{"registry:push", "registry:delete"},
|
|
Roles: []string{"ci"},
|
|
}
|
|
if err := d.UpdatePolicyRule(id, updates); err != nil {
|
|
t.Fatalf("UpdatePolicyRule: %v", err)
|
|
}
|
|
|
|
got, err := d.GetPolicyRule(id)
|
|
if err != nil {
|
|
t.Fatalf("GetPolicyRule: %v", err)
|
|
}
|
|
if got.Effect != "deny" {
|
|
t.Fatalf("effect: got %q, want %q", got.Effect, "deny")
|
|
}
|
|
if len(got.Actions) != 2 {
|
|
t.Fatalf("actions: got %d, want 2", len(got.Actions))
|
|
}
|
|
if len(got.Roles) != 1 || got.Roles[0] != "ci" {
|
|
t.Fatalf("roles: got %v, want [ci]", got.Roles)
|
|
}
|
|
}
|
|
|
|
func TestUpdatePolicyRuleNotFound(t *testing.T) {
|
|
d := migratedTestDB(t)
|
|
|
|
err := d.UpdatePolicyRule(9999, PolicyRuleRow{Description: "nope"})
|
|
if !errors.Is(err, ErrPolicyRuleNotFound) {
|
|
t.Fatalf("expected ErrPolicyRuleNotFound, got %v", err)
|
|
}
|
|
}
|
|
|
|
func TestSetPolicyRuleEnabled(t *testing.T) {
|
|
d := migratedTestDB(t)
|
|
|
|
rule := PolicyRuleRow{
|
|
Priority: 50,
|
|
Description: "test",
|
|
Effect: "allow",
|
|
Actions: []string{"registry:pull"},
|
|
Enabled: true,
|
|
}
|
|
id, err := d.CreatePolicyRule(rule)
|
|
if err != nil {
|
|
t.Fatalf("CreatePolicyRule: %v", err)
|
|
}
|
|
|
|
// Disable the rule.
|
|
if err := d.SetPolicyRuleEnabled(id, false); err != nil {
|
|
t.Fatalf("SetPolicyRuleEnabled(false): %v", err)
|
|
}
|
|
|
|
got, err := d.GetPolicyRule(id)
|
|
if err != nil {
|
|
t.Fatalf("GetPolicyRule: %v", err)
|
|
}
|
|
if got.Enabled {
|
|
t.Fatal("enabled: got true, want false")
|
|
}
|
|
|
|
// Re-enable.
|
|
if err := d.SetPolicyRuleEnabled(id, true); err != nil {
|
|
t.Fatalf("SetPolicyRuleEnabled(true): %v", err)
|
|
}
|
|
|
|
got, err = d.GetPolicyRule(id)
|
|
if err != nil {
|
|
t.Fatalf("GetPolicyRule: %v", err)
|
|
}
|
|
if !got.Enabled {
|
|
t.Fatal("enabled: got false, want true")
|
|
}
|
|
}
|
|
|
|
func TestSetPolicyRuleEnabledNotFound(t *testing.T) {
|
|
d := migratedTestDB(t)
|
|
|
|
err := d.SetPolicyRuleEnabled(9999, true)
|
|
if !errors.Is(err, ErrPolicyRuleNotFound) {
|
|
t.Fatalf("expected ErrPolicyRuleNotFound, got %v", err)
|
|
}
|
|
}
|
|
|
|
func TestDeletePolicyRule(t *testing.T) {
|
|
d := migratedTestDB(t)
|
|
|
|
rule := PolicyRuleRow{
|
|
Priority: 50,
|
|
Description: "to delete",
|
|
Effect: "allow",
|
|
Actions: []string{"registry:pull"},
|
|
Enabled: true,
|
|
}
|
|
|
|
id, err := d.CreatePolicyRule(rule)
|
|
if err != nil {
|
|
t.Fatalf("CreatePolicyRule: %v", err)
|
|
}
|
|
|
|
if err := d.DeletePolicyRule(id); err != nil {
|
|
t.Fatalf("DeletePolicyRule: %v", err)
|
|
}
|
|
|
|
// Verify it's gone.
|
|
_, err = d.GetPolicyRule(id)
|
|
if !errors.Is(err, ErrPolicyRuleNotFound) {
|
|
t.Fatalf("expected ErrPolicyRuleNotFound after delete, got %v", err)
|
|
}
|
|
}
|
|
|
|
func TestDeletePolicyRuleNotFound(t *testing.T) {
|
|
d := migratedTestDB(t)
|
|
|
|
err := d.DeletePolicyRule(9999)
|
|
if !errors.Is(err, ErrPolicyRuleNotFound) {
|
|
t.Fatalf("expected ErrPolicyRuleNotFound, got %v", err)
|
|
}
|
|
}
|