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 userStoreHarness struct { Context context.Context Store iam.UserStore } func testUserStore(t *testing.T, setup func(t *testing.T) userStoreHarness) { t.Run("Save and Get", func(t *testing.T) { h := setup(t) u := &iam.User{ID: core.NewID("t"), Email: "a@test.com", Verified: true} err := h.Store.Save(h.Context, u) assert.Ok(t, err) var got iam.User assert.Ok(t, h.Store.Get(h.Context, u.ID, &got)) assert.Eq(t, got, *u) }) t.Run("Save: access denied", func(t *testing.T) { h := setup(t) // anonymous access comes from context anon := t.Context() u := iam.User{ID: core.NewID("t"), Email: "a@test.com", Verified: true} err := h.Store.Save(anon, &u) assert.Eq(t, err, iam.ErrForbidden) }) t.Run("Get: access denied", func(t *testing.T) { h := setup(t) u := iam.User{ID: core.NewID("t"), Email: "a@test.com", Verified: true} // create user h.Store.Save(h.Context, &u) // anonymous access comes from context anon := t.Context() // verify requires access check var g iam.User err := h.Store.Get(anon, u.ID, &g) assert.Eq(t, err, iam.ErrForbidden) }) t.Run("GetByEmail: access denied", func(t *testing.T) { h := setup(t) u := &iam.User{ID: core.NewID("t"), Email: "d@test.com", Verified: true} err := h.Store.Save(h.Context, u) assert.Ok(t, err) // anonymous access comes from context anon := t.Context() var got iam.User err = h.Store.GetByEmail(anon, "d@test.com", &got) assert.Eq(t, err, iam.ErrForbidden) }) t.Run("Verify: access denied", func(t *testing.T) { h := setup(t) u := iam.User{ID: core.NewID("t"), Email: "a@test.com", Verified: true} // create user h.Store.Save(h.Context, &u) // anonymous access comes from context anon := t.Context() // verify requires access check err := h.Store.Verify(anon, u.ID) // TODO assert that a specific Cap is missing. assert.Eq(t, err, iam.ErrForbidden) }) t.Run("List: access denied", func(t *testing.T) { h := setup(t) // empty access denies all checks access := lib.Access{} ctx := lib.PutAccess(h.Context, access) var page core.Page[iam.User] err := h.Store.List(ctx, core.PageReq{}, &page) assert.Eq(t, err, iam.ErrForbidden) }) t.Run("Save fails when ID is empty", func(t *testing.T) { h := setup(t) u := &iam.User{Email: "b@test.com"} err := h.Store.Save(h.Context, u) assert.Eq(t, err, iam.ErrUserIDEmpty) }) t.Run("GetByEmail", func(t *testing.T) { h := setup(t) u := &iam.User{ID: core.NewID("t"), Email: "d@test.com", Verified: true} err := h.Store.Save(h.Context, u) assert.Ok(t, err) var got iam.User err = h.Store.GetByEmail(h.Context, "d@test.com", &got) assert.Ok(t, err) assert.Eq(t, got.ID, u.ID) assert.Eq(t, got.Email, u.Email) assert.Eq(t, got.Verified, true) }) t.Run("GetByEmail not found", func(t *testing.T) { h := setup(t) var got iam.User err := h.Store.GetByEmail(h.Context, "nobody@test.com", &got) assert.Eq(t, err, core.ErrNotFound) }) t.Run("Get not found", func(t *testing.T) { h := setup(t) var got iam.User err := h.Store.Get(h.Context, core.NewID("t"), &got) assert.Eq(t, err, core.ErrNotFound) }) t.Run("Get with empty ID", func(t *testing.T) { h := setup(t) var got iam.User err := h.Store.Get(h.Context, core.ID{}, &got) assert.Eq(t, err, core.ErrNotFound) }) t.Run("Verify", func(t *testing.T) { h := setup(t) u := &iam.User{ID: core.NewID("t"), Email: "e@test.com", Verified: false} err := h.Store.Save(h.Context, u) assert.Ok(t, err) assert.Ok(t, h.Store.Verify(h.Context, u.ID)) var got iam.User err = h.Store.Get(h.Context, u.ID, &got) assert.Ok(t, err) assert.Eq(t, got.Verified, true) }) t.Run("Verify nonexistent user", func(t *testing.T) { h := setup(t) // TODO not sure this is what I want. // no error expected; UPDATE simply affects 0 rows assert.Ok(t, h.Store.Verify(h.Context, core.NewID("t"))) }) t.Run("List", func(t *testing.T) { h := setup(t) ids := generateIDs(3) for _, id := range ids { u := &iam.User{ID: id, Email: id.String() + "@test.com"} assert.Ok(t, h.Store.Save(h.Context, u)) } var page core.Page[iam.User] err := h.Store.List(h.Context, core.PageReq{}, &page) assert.Ok(t, err) assert.Eq(t, len(page.Items), 3) }) t.Run("List empty", func(t *testing.T) { h := setup(t) var page core.Page[iam.User] err := h.Store.List(h.Context, core.PageReq{}, &page) assert.Ok(t, err) assert.Eq(t, len(page.Items), 0) assert.Eq(t, page.Cursor, "") }) t.Run("List pagination", func(t *testing.T) { h := setup(t) ids := generateIDs(210) for _, id := range ids { u := &iam.User{ID: id, Email: id.String() + "@test.com"} assert.Ok(t, h.Store.Save(h.Context, u)) } var page core.Page[iam.User] err := h.Store.List(h.Context, core.PageReq{}, &page) assert.Ok(t, err) assert.Eq(t, len(page.Items), 100) assert.Eq(t, page.Items[0].ID, ids[0]) assert.Eq(t, page.Items[99].ID, ids[99]) var page2 core.Page[iam.User] err = h.Store.List(h.Context, core.PageReq{Cursor: page.Cursor}, &page2) assert.Ok(t, err) assert.Eq(t, len(page2.Items), 100) assert.Eq(t, page2.Items[0].ID, ids[100]) assert.Eq(t, page2.Items[99].ID, ids[199]) var page3 core.Page[iam.User] err = h.Store.List(h.Context, core.PageReq{Cursor: page2.Cursor}, &page3) assert.Ok(t, err) assert.Eq(t, len(page3.Items), 10) assert.Eq(t, page3.Cursor, "") assert.Eq(t, page3.Items[0].ID, ids[200]) assert.Eq(t, page3.Items[9].ID, ids[209]) }) } func TestSqliteUserStore(t *testing.T) { testUserStore(t, func(t *testing.T) userStoreHarness { t.Helper() db := setupTestDB(t) tx, err := db.Begin() assert.Ok(t, err) t.Cleanup(func() { tx.Rollback() }) guard := lib.ContextAccessGuard{} ctx := lib.PutAccess(t.Context(), lib.AdminAccess()) userStore := store.NewSqliteUserStore(dbi.WrapTx(tx), guard) return userStoreHarness{ Context: ctx, Store: userStore, } }) }