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:
2026-07-16 12:24:36 +08:00
parent 6604ecb026
commit a18f96912d
30 changed files with 1259 additions and 394 deletions
+4 -11
View File
@@ -2,7 +2,6 @@ package server
import (
"bytes"
"context"
"encoding/json"
"net/http"
"net/http/httptest"
@@ -14,6 +13,7 @@ import (
"github.com/dhao2001/mygo/internal/config"
"github.com/dhao2001/mygo/internal/repository"
"github.com/dhao2001/mygo/internal/service"
"github.com/dhao2001/mygo/internal/testutil"
)
func TestVersionRoute(t *testing.T) {
@@ -25,7 +25,7 @@ func TestVersionRoute(t *testing.T) {
},
}
authService := service.NewAuthService(nil, nil, nil, nil, 15*time.Minute, 7*24*time.Hour)
webApp := app.NewWebApp(cfg, nil, nil, nil, nil, nil, authService, nil, nil, nil)
webApp := app.NewWebApp(cfg, nil, authService, nil, nil, nil)
router := NewRouter(webApp)
req := httptest.NewRequest(http.MethodGet, "/api/v1/version", nil)
@@ -94,7 +94,7 @@ func TestAdminRoutes(t *testing.T) {
},
}
webApp := app.NewWebApp(cfg, db, userRepo, sessionRepo, nil, credentialRepo, authService, adminService, nil, nil)
webApp := app.NewWebApp(cfg, db, authService, adminService, nil, nil)
router := NewRouter(webApp)
// Helper: register a user via POST /api/v1/auth/register and return the user ID.
@@ -123,14 +123,7 @@ func TestAdminRoutes(t *testing.T) {
// Register admin user and promote to admin.
adminID := register("admin", "admin@test.local", "adminpass1")
adminUser, err := userRepo.FindByID(context.Background(), adminID)
if err != nil {
t.Fatalf("find admin user: %v", err)
}
adminUser.IsAdmin = true
if err := userRepo.Update(context.Background(), adminUser); err != nil {
t.Fatalf("promote to admin: %v", err)
}
testutil.SetUserAdmin(t, db, adminID, true)
// Generate admin JWT.
adminToken, err := auth.GenerateAccessToken(adminID, jwtSecret, accessTTL)