package store_test import ( "context" "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 tenantStoreTestHarness struct { Context context.Context Store iam.TenantStore NewUsers func(t *testing.T, n int) []core.ID AddMember func(t *testing.T, tenantID, userID core.ID) } func testTenantStore(t *testing.T, setup func(t *testing.T) tenantStoreTestHarness) { t.Run("Create and Get", func(t *testing.T) { h := setup(t) ctx := h.Context in := &iam.Tenant{ID: core.NewID("t"), Name: "Acme"} assert.Ok(t, h.Store.Create(ctx, in)) var got iam.Tenant err := h.Store.Get(ctx, in.ID, &got) assert.Ok(t, err) assert.Eq(t, got, *in) }) t.Run("Create rejects empty ID", func(t *testing.T) { h := setup(t) err := h.Store.Create(h.Context, &iam.Tenant{Name: "Acme"}) assert.Eq(t, err, iam.ErrTenantIDEmpty) }) t.Run("Update changes name", func(t *testing.T) { h := setup(t) ctx := h.Context id := core.NewID("t") assert.Ok(t, h.Store.Create(ctx, &iam.Tenant{ID: id, Name: "Old"})) assert.Ok(t, h.Store.Update(ctx, &iam.Tenant{ID: id, Name: "New"})) var got iam.Tenant assert.Ok(t, h.Store.Get(ctx, id, &got)) assert.Eq(t, got.Name, "New") }) t.Run("Update not found", func(t *testing.T) { h := setup(t) err := h.Store.Update(h.Context, &iam.Tenant{ID: core.NewID("t"), Name: "Nope"}) assert.Eq(t, err, core.ErrNotFound) }) t.Run("Update rejects empty ID", func(t *testing.T) { h := setup(t) err := h.Store.Update(h.Context, &iam.Tenant{Name: "Nope"}) assert.Eq(t, err, iam.ErrTenantIDEmpty) }) t.Run("Get not found", func(t *testing.T) { h := setup(t) var got iam.Tenant err := h.Store.Get(h.Context, core.NewID("t"), &got) assert.Eq(t, err, core.ErrNotFound) }) t.Run("Delete", func(t *testing.T) { h := setup(t) ctx := h.Context id := core.NewID("t") assert.Ok(t, h.Store.Create(ctx, &iam.Tenant{ID: id, Name: "Acme"})) assert.Ok(t, h.Store.Delete(ctx, id)) var got iam.Tenant err := h.Store.Get(ctx, id, &got) assert.Eq(t, err, core.ErrNotFound) }) t.Run("List", func(t *testing.T) { h := setup(t) ctx := h.Context ids := generateIDs(3) for _, id := range ids { assert.Ok(t, h.Store.Create(ctx, &iam.Tenant{ID: id, Name: "T"})) } var page core.Page[iam.Tenant] err := h.Store.List(ctx, core.PageReq{}, &page) assert.Ok(t, err) assert.Eq(t, len(page.Items) >= 3, true) }) t.Run("List pagination", func(t *testing.T) { h := setup(t) ctx := h.Context // Create 210 tenants. // Pages: 100, 100, 10. ids := generateIDs(210) for _, id := range ids { assert.Ok(t, h.Store.Create(ctx, &iam.Tenant{ID: id, Name: "T"})) } var page core.Page[iam.Tenant] err := h.Store.List(ctx, core.PageReq{}, &page) assert.Ok(t, err) assert.Eq(t, len(page.Items), 100) var page2 core.Page[iam.Tenant] err = h.Store.List(ctx, core.PageReq{Cursor: page.Cursor}, &page2) assert.Ok(t, err) assert.Eq(t, len(page2.Items), 100) var page3 core.Page[iam.Tenant] err = h.Store.List(ctx, core.PageReq{Cursor: page2.Cursor}, &page3) assert.Ok(t, err) assert.Eq(t, len(page3.Items), 10) assert.Eq(t, page3.Cursor, "") }) t.Run("List own tenants", func(t *testing.T) { h := setup(t) ctx := h.Context userID := h.NewUsers(t, 1)[0] ids := generateIDs(3) for _, id := range ids { assert.Ok(t, h.Store.Create(ctx, &iam.Tenant{ID: id, Name: "T"})) } // Add user to 2 of the 3 tenants. h.AddMember(t, ids[0], userID) h.AddMember(t, ids[1], userID) userCtx := lib.PutAccess(t.Context(), lib.Access{}) userCtx = iam.PutPrincipal(userCtx, iam.Principal{Subject: userID.String()}) var page core.Page[iam.Tenant] err := h.Store.List(userCtx, core.PageReq{}, &page) assert.Ok(t, err) assert.Eq(t, len(page.Items), 2) assert.Eq(t, page.Items[0].ID, ids[0]) assert.Eq(t, page.Items[1].ID, ids[1]) }) t.Run("Create: no access", func(t *testing.T) { h := setup(t) err := h.Store.Create(t.Context(), &iam.Tenant{ID: core.NewID("t")}) assert.Eq(t, err, iam.ErrForbidden) }) t.Run("Update: no access", func(t *testing.T) { h := setup(t) err := h.Store.Update(t.Context(), &iam.Tenant{ID: core.NewID("t")}) assert.Eq(t, err, iam.ErrForbidden) }) t.Run("Get: no access", func(t *testing.T) { h := setup(t) var got iam.Tenant err := h.Store.Get(t.Context(), core.NewID("t"), &got) assert.Eq(t, err, iam.ErrForbidden) }) t.Run("Delete: no access", func(t *testing.T) { h := setup(t) err := h.Store.Delete(t.Context(), core.NewID("t")) assert.Eq(t, err, iam.ErrForbidden) }) } func TestSqliteTenantStore(t *testing.T) { testTenantStore(t, func(t *testing.T) tenantStoreTestHarness { 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) userStore := store.NewSqliteUserStore(w, guard) memberStore := store.NewSqliteTenantMemberStore(w, guard) return tenantStoreTestHarness{ Context: ctx, Store: tenantStore, NewUsers: func(t *testing.T, n int) []core.ID { t.Helper() ids := generateIDs(n) for _, id := range ids { err := userStore.Save(ctx, &iam.User{ID: id, Email: id.String() + "@test.com", Verified: true}) assert.Ok(t, err) } return ids }, AddMember: func(t *testing.T, tenantID, userID core.ID) { t.Helper() assert.Ok(t, memberStore.Create(ctx, iam.TenantMember{Tenant: tenantID, UserID: userID})) }, } }) }