feat(repository): refactor query/mutation ports with capability-safe
interfaces - refactor: split UserRepository into AuthUserRepository and AdminUserRepository capabilities - refactor: split SessionRepository into AuthSessionRepository and repository-owned CLI/admin methods - refactor: replace generic Repository Update/Delete with operation-specific params and ownership predicates - refactor: replace CredentialRepository generic CRUD with passkey-specific methods - refactor: replace FileRepository generic Create/Update/Delete with UploadedFileParams, DirectoryParams, and owned soft-delete - refactor: remove repository fields from WebApp struct; repositories are now composition-time wiring only - feat: add domain error kinds ErrParentNotFound, ErrParentNotDir, ErrDirectoryNotEmpty, ErrInvalidMove - feat: add CredentialTypeAppPasskey constant - feat: add testutil.SetUserAdmin for test fixture setup that bypasses production service ports - test: add architecture test banning GORM Save in repository package - test: add capability interface contract tests ensuring each service receives the minimal interface - test: add blockingStorage helper for concurrent promotion tests - test: add preserved DSN parameter test for sqliteImmediateDSN - docs: update architecture decisions with repository write rules and capability separation - docs: update roadmap to clarify atomic single-use refresh sessions - docs: add -race test target to development docs
This commit is contained in:
@@ -17,9 +17,10 @@ import (
|
||||
"github.com/dhao2001/mygo/internal/model"
|
||||
"github.com/dhao2001/mygo/internal/repository"
|
||||
"github.com/dhao2001/mygo/internal/service"
|
||||
"github.com/dhao2001/mygo/internal/testutil"
|
||||
)
|
||||
|
||||
func setupAdminRouter(t *testing.T) (*gin.Engine, repository.UserRepository) {
|
||||
func setupAdminRouter(t *testing.T) (*gin.Engine, repository.UserRepository, *gorm.DB) {
|
||||
t.Helper()
|
||||
|
||||
db, err := gorm.Open(sqlite.Open(":memory:"), &gorm.Config{})
|
||||
@@ -63,7 +64,7 @@ func setupAdminRouter(t *testing.T) (*gin.Engine, repository.UserRepository) {
|
||||
admin.DELETE("/users/:id", adminHandler.DeleteUser)
|
||||
}
|
||||
|
||||
return r, userRepo
|
||||
return r, userRepo, db
|
||||
}
|
||||
|
||||
// registerHelper creates a user via the HTTP endpoint and returns the user ID.
|
||||
@@ -113,24 +114,16 @@ func loginHelper(t *testing.T, r *gin.Engine, email, password string) string {
|
||||
}
|
||||
|
||||
// makeAdminHelper promotes a user to admin in the database.
|
||||
func makeAdminHelper(t *testing.T, userRepo repository.UserRepository, userID string) {
|
||||
func makeAdminHelper(t *testing.T, db *gorm.DB, userID string) {
|
||||
t.Helper()
|
||||
|
||||
user, err := userRepo.FindByID(context.Background(), userID)
|
||||
if err != nil {
|
||||
t.Fatalf("find user %s: %v", userID, err)
|
||||
}
|
||||
user.IsAdmin = true
|
||||
if err := userRepo.Update(context.Background(), user); err != nil {
|
||||
t.Fatalf("update user: %v", err)
|
||||
}
|
||||
testutil.SetUserAdmin(t, db, userID, true)
|
||||
}
|
||||
|
||||
func TestAdminDeleteUser(t *testing.T) {
|
||||
r, userRepo := setupAdminRouter(t)
|
||||
r, _, db := setupAdminRouter(t)
|
||||
|
||||
adminID := registerHelper(t, r, "admin", "admin@example.com", "adminpass123")
|
||||
makeAdminHelper(t, userRepo, adminID)
|
||||
makeAdminHelper(t, db, adminID)
|
||||
|
||||
userID := registerHelper(t, r, "bob", "bob@example.com", "bobpass123")
|
||||
|
||||
@@ -159,10 +152,10 @@ func TestAdminDeleteUser(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestAdminDeleteUserForbidden(t *testing.T) {
|
||||
r, userRepo := setupAdminRouter(t)
|
||||
r, _, db := setupAdminRouter(t)
|
||||
|
||||
adminID := registerHelper(t, r, "admin", "admin@example.com", "adminpass123")
|
||||
makeAdminHelper(t, userRepo, adminID)
|
||||
makeAdminHelper(t, db, adminID)
|
||||
|
||||
userID := registerHelper(t, r, "bob", "bob@example.com", "bobpass123")
|
||||
|
||||
@@ -181,7 +174,7 @@ func TestAdminDeleteUserForbidden(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestAdminDeleteUserUnauthorized(t *testing.T) {
|
||||
r, _ := setupAdminRouter(t)
|
||||
r, _, _ := setupAdminRouter(t)
|
||||
|
||||
req := httptest.NewRequest(http.MethodDelete, "/api/v1/admin/users/some-id", nil)
|
||||
rec := httptest.NewRecorder()
|
||||
@@ -193,10 +186,10 @@ func TestAdminDeleteUserUnauthorized(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestAdminGetUser(t *testing.T) {
|
||||
r, userRepo := setupAdminRouter(t)
|
||||
r, _, db := setupAdminRouter(t)
|
||||
|
||||
adminID := registerHelper(t, r, "admin", "admin@example.com", "adminpass123")
|
||||
makeAdminHelper(t, userRepo, adminID)
|
||||
makeAdminHelper(t, db, adminID)
|
||||
|
||||
userID := registerHelper(t, r, "bob", "bob@example.com", "bobpass123")
|
||||
|
||||
@@ -231,15 +224,15 @@ func TestAdminGetUser(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestAdminGetDeletedUser(t *testing.T) {
|
||||
r, userRepo := setupAdminRouter(t)
|
||||
r, userRepo, db := setupAdminRouter(t)
|
||||
|
||||
adminID := registerHelper(t, r, "admin", "admin@example.com", "adminpass123")
|
||||
makeAdminHelper(t, userRepo, adminID)
|
||||
makeAdminHelper(t, db, adminID)
|
||||
|
||||
userID := registerHelper(t, r, "bob", "bob@example.com", "bobpass123")
|
||||
|
||||
// Soft-delete regular user directly via repo
|
||||
if err := userRepo.Delete(context.Background(), userID); err != nil {
|
||||
if err := userRepo.MarkAdminDeleted(context.Background(), userID); err != nil {
|
||||
t.Fatalf("soft-delete user: %v", err)
|
||||
}
|
||||
|
||||
@@ -267,17 +260,17 @@ func TestAdminGetDeletedUser(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestAdminListIncludesDeleted(t *testing.T) {
|
||||
r, userRepo := setupAdminRouter(t)
|
||||
r, userRepo, db := setupAdminRouter(t)
|
||||
|
||||
adminID := registerHelper(t, r, "admin", "admin@example.com", "adminpass123")
|
||||
makeAdminHelper(t, userRepo, adminID)
|
||||
makeAdminHelper(t, db, adminID)
|
||||
|
||||
// Create 2 regular users
|
||||
_ = registerHelper(t, r, "alice", "alice@example.com", "alicepass123")
|
||||
bobID := registerHelper(t, r, "bob", "bob@example.com", "bobpass123")
|
||||
|
||||
// Soft-delete bob
|
||||
if err := userRepo.Delete(context.Background(), bobID); err != nil {
|
||||
if err := userRepo.MarkAdminDeleted(context.Background(), bobID); err != nil {
|
||||
t.Fatalf("soft-delete user: %v", err)
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user