package store_test import ( "context" "fmt" "testing" "atlas9.dev/c/core" "atlas9.dev/c/core/assert" "atlas9.dev/c/core/dbi" "atlas9.dev/c/core/iam" "atlas9.dev/c/demo/lib" "atlas9.dev/c/demo/store" ) type roleTestHarness struct { Context context.Context Store iam.RoleStore NewTenants func(t *testing.T, n int) []core.ID } func testRoleStore(t *testing.T, setup func(t *testing.T) roleTestHarness) { t.Run("Save and Get", func(t *testing.T) { h := setup(t) ctx := h.Context tenant := h.NewTenants(t, 1)[0] in := &iam.Role{Slug: "viewer", Tenant: tenant, Name: "Viewer", Caps: []iam.Cap{"read"}} assert.Ok(t, h.Store.Save(ctx, in)) var got iam.Role err := h.Store.Get(ctx, tenant, "viewer", &got) assert.Ok(t, err) assert.Eq(t, got.Slug, in.Slug) assert.Eq(t, got.Name, in.Name) }) t.Run("Save updates name and caps", func(t *testing.T) { h := setup(t) ctx := h.Context tenant := h.NewTenants(t, 1)[0] assert.Ok(t, h.Store.Save(ctx, &iam.Role{Slug: "r", Tenant: tenant, Name: "Old", Caps: []iam.Cap{"read"}})) assert.Ok(t, h.Store.Save(ctx, &iam.Role{Slug: "r", Tenant: tenant, Name: "New", Caps: []iam.Cap{"read", "write"}})) var got iam.Role err := h.Store.Get(ctx, tenant, "r", &got) assert.Ok(t, err) assert.Eq(t, got.Name, "New") }) t.Run("Get not found", func(t *testing.T) { h := setup(t) tenant := h.NewTenants(t, 1)[0] var got iam.Role err := h.Store.Get(h.Context, tenant, "nonexistent", &got) assert.Eq(t, err != nil, true) }) t.Run("Delete", func(t *testing.T) { h := setup(t) ctx := h.Context tenant := h.NewTenants(t, 1)[0] assert.Ok(t, h.Store.Save(ctx, &iam.Role{Slug: "r", Tenant: tenant, Name: "R"})) assert.Ok(t, h.Store.Delete(ctx, tenant, "r")) var got iam.Role err := h.Store.Get(ctx, tenant, "r", &got) assert.Eq(t, err != nil, true) }) t.Run("Delete not found", func(t *testing.T) { h := setup(t) tenant := h.NewTenants(t, 1)[0] err := h.Store.Delete(h.Context, tenant, "nonexistent") assert.Eq(t, err != nil, true) }) t.Run("List", func(t *testing.T) { h := setup(t) ctx := h.Context tenant := h.NewTenants(t, 1)[0] for i := range 3 { slug := fmt.Sprintf("role-%06d", i) assert.Ok(t, h.Store.Save(ctx, &iam.Role{Slug: slug, Tenant: tenant, Name: slug})) } var page core.Page[iam.Role] err := h.Store.List(ctx, tenant, core.PageReq{}, &page) assert.Ok(t, err) assert.Eq(t, len(page.Items), 3) assert.Eq(t, page.Items[0].Slug, "role-000000") assert.Eq(t, page.Items[2].Slug, "role-000002") }) t.Run("List pagination", func(t *testing.T) { h := setup(t) ctx := h.Context tenant := h.NewTenants(t, 1)[0] for i := range 110 { slug := fmt.Sprintf("role-%06d", i) assert.Ok(t, h.Store.Save(ctx, &iam.Role{Slug: slug, Tenant: tenant, Name: slug})) } var page core.Page[iam.Role] err := h.Store.List(ctx, tenant, core.PageReq{}, &page) assert.Ok(t, err) assert.Eq(t, len(page.Items), 100) var page2 core.Page[iam.Role] err = h.Store.List(ctx, tenant, core.PageReq{Cursor: page.Cursor}, &page2) assert.Ok(t, err) assert.Eq(t, len(page2.Items), 10) assert.Eq(t, page2.Cursor, "") }) t.Run("List isolation", func(t *testing.T) { h := setup(t) ctx := h.Context tenants := h.NewTenants(t, 2) assert.Ok(t, h.Store.Save(ctx, &iam.Role{Slug: "r1", Tenant: tenants[0], Name: "R1"})) assert.Ok(t, h.Store.Save(ctx, &iam.Role{Slug: "r2", Tenant: tenants[0], Name: "R2"})) var page core.Page[iam.Role] err := h.Store.List(ctx, tenants[1], core.PageReq{}, &page) assert.Ok(t, err) assert.Eq(t, len(page.Items), 0) }) t.Run("Save: no access", func(t *testing.T) { h := setup(t) tenant := h.NewTenants(t, 1)[0] err := h.Store.Save(t.Context(), &iam.Role{Slug: "r", Tenant: tenant, Name: "R"}) assert.Eq(t, err, iam.ErrForbidden) }) t.Run("Delete: no access", func(t *testing.T) { h := setup(t) ctx := h.Context tenant := h.NewTenants(t, 1)[0] assert.Ok(t, h.Store.Save(ctx, &iam.Role{Slug: "r", Tenant: tenant, Name: "R"})) assert.Eq(t, iam.ErrForbidden, h.Store.Delete(t.Context(), tenant, "r")) }) } func TestSqliteRoleStore(t *testing.T) { testRoleStore(t, func(t *testing.T) roleTestHarness { t.Helper() db := setupTestDB(t) tx, err := db.Begin() assert.Ok(t, err) t.Cleanup(func() { tx.Rollback() }) w := dbi.WrapTx(tx) guard := lib.ContextAccessGuard{} ctx := lib.PutAccess(t.Context(), lib.AdminAccess()) tenantStore := store.NewSqliteTenantStore(w, guard) return roleTestHarness{ Context: ctx, Store: store.NewSqliteRoleStore(w, guard), NewTenants: func(t *testing.T, n int) []core.ID { t.Helper() ids := generateIDs(n) for _, id := range ids { err := tenantStore.Create(ctx, &iam.Tenant{ID: id, Name: "T"}) assert.Ok(t, err) } return ids }, } }) }