package store_test import ( "errors" "strings" "testing" "atlas9.dev/c/core" "atlas9.dev/c/core/assert" "atlas9.dev/c/core/dbi" "atlas9.dev/c/demo/lib/bots" "atlas9.dev/c/demo/lib/ents" "atlas9.dev/c/demo/lib/access" "atlas9.dev/c/demo/store" ) // TestQuotaEnforcement verifies that quota_check triggers fire, that wrapQuotaErr // matches the trigger message prefix, and that the quota name reaches the caller. // If a trigger message is ever reworded, either the errors.Is or the Name assertion // will catch the mismatch before it silently becomes a 500. func TestQuotaEnforcement(t *testing.T) { db := setupTestDB(t) tx, err := db.Begin() assert.Ok(t, err) t.Cleanup(func() { tx.Rollback() }) w := dbi.WrapTx(tx) ctx := t.Context() guard := access.AllowAll{} tenantID := core.NewID("t") _, err = w.Exec(ctx, `INSERT INTO tenants (id, name) VALUES ($1, 'T')`, tenantID) assert.Ok(t, err) // Each max=1: the first insert succeeds, the second hits the trigger. _, err = w.Exec(ctx, ` INSERT INTO entitlements (tenant, bots_max, webhooks_max, slack_max) VALUES ($1, 1, 1, 1)`, tenantID) assert.Ok(t, err) botStore := store.NewSqliteBotStore(w, guard) // bots: within quota. err = botStore.Create(ctx, &bots.Bot{ID: core.NewID("bot"), Tenant: tenantID, Name: "b1"}) assert.Ok(t, err) // bots: over quota — trigger fires, wrapQuotaErr translates it. err = botStore.Create(ctx, &bots.Bot{ID: core.NewID("bot"), Tenant: tenantID, Name: "b2"}) if !errors.Is(err, ents.ErrQuotaExceeded) { t.Fatalf("expected ErrQuotaExceeded, got %v", err) } var qe *ents.QuotaExceededError errors.As(err, &qe) assert.Eq(t, qe.Name, "bots") // webhooks: insert directly (bypassing the store's sealed-secret guard) to // exercise the quota_check trigger and confirm its message names the quota. insertWebhook := func(id string) error { _, err := w.Exec(ctx, ` INSERT INTO webhook_endpoints (id, tenant, name, url, dek_id, secret_enc) VALUES ($1, $2, 'w', 'https://x.test', $3, $4)`, core.NewID(id), tenantID, core.NewID("dek"), []byte("x")) return err } assert.Ok(t, insertWebhook("whk")) assertQuotaRaise(t, insertWebhook("whk"), "webhooks") // slack: same, on its own table and trigger. insertSlack := func(id string) error { _, err := w.Exec(ctx, ` INSERT INTO slack_endpoints (id, tenant, name, dek_id, url_enc) VALUES ($1, $2, 's', $3, $4)`, core.NewID(id), tenantID, core.NewID("dek"), []byte("x")) return err } assert.Ok(t, insertSlack("slk")) assertQuotaRaise(t, insertSlack("slk"), "slack") } // assertQuotaRaise checks that err is the raw quota_check trigger abort naming // the given quota. wrapQuotaErr (store-internal) translates this into an // *ents.QuotaExceededError on the real write path; the bots case above covers // that translation, so here we assert the trigger message the stores rely on. func assertQuotaRaise(t *testing.T, err error, quota string) { t.Helper() want := "quota exceeded: " + quota if err == nil || !strings.Contains(err.Error(), want) { t.Fatalf("expected %q, got %v", want, err) } }