refactor(api): enforce protocol-neutral service boundaries
- refactor: move HTTP status and DTO concerns out of model and service layers into API and handler code. - feat: add admin service boundary and route auth through services instead of direct repository access. - test: add architecture and error tests covering package boundaries, redaction, and service behavior. - docs: record the protocol-neutral boundary decision and update architecture and roadmap notes.
This commit is contained in:
@@ -5,7 +5,6 @@ import (
|
||||
"context"
|
||||
"errors"
|
||||
"io"
|
||||
"net/http"
|
||||
"strings"
|
||||
"sync"
|
||||
"testing"
|
||||
@@ -18,16 +17,16 @@ import (
|
||||
"github.com/dhao2001/mygo/internal/storage"
|
||||
)
|
||||
|
||||
// assertAppError checks that err is an *model.AppError with the expected status.
|
||||
func assertAppError(t *testing.T, err error, wantStatus int) bool {
|
||||
// assertAppError checks that err is an *model.AppError with the expected kind.
|
||||
func assertAppError(t *testing.T, err error, wantKind model.ErrorKind) bool {
|
||||
t.Helper()
|
||||
var ae *model.AppError
|
||||
if !errors.As(err, &ae) {
|
||||
t.Errorf("expected *model.AppError, got %T: %v", err, err)
|
||||
return false
|
||||
}
|
||||
if ae.Status != wantStatus {
|
||||
t.Errorf("status = %d, want %d; message = %q", ae.Status, wantStatus, ae.Message)
|
||||
if ae.Kind != wantKind {
|
||||
t.Errorf("kind = %q, want %q; message = %q", ae.Kind, wantKind, ae.Message)
|
||||
return false
|
||||
}
|
||||
return true
|
||||
@@ -151,7 +150,7 @@ func TestFileService_UploadRejectsOversizedFileAndCleansStaging(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
|
||||
_, err := svc.Upload(ctx, "user1", nil, "too-large.txt", strings.NewReader("123456"))
|
||||
assertAppError(t, err, http.StatusRequestEntityTooLarge)
|
||||
assertAppError(t, err, model.KindPayloadTooLarge)
|
||||
|
||||
store.mu.RLock()
|
||||
defer store.mu.RUnlock()
|
||||
@@ -285,7 +284,7 @@ func TestFileService_DownloadForbidden(t *testing.T) {
|
||||
}
|
||||
|
||||
_, _, err = svc.Download(ctx, "user2", info.ID)
|
||||
assertAppError(t, err, http.StatusForbidden)
|
||||
assertAppError(t, err, model.KindPermissionDenied)
|
||||
}
|
||||
|
||||
func TestFileService_DownloadDirectory(t *testing.T) {
|
||||
@@ -326,7 +325,7 @@ func TestFileService_GetNotFound(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
|
||||
_, err := svc.Get(ctx, "user1", "nonexistent")
|
||||
assertAppError(t, err, http.StatusNotFound)
|
||||
assertAppError(t, err, model.KindNotFound)
|
||||
}
|
||||
|
||||
func TestFileService_GetForbidden(t *testing.T) {
|
||||
@@ -339,7 +338,7 @@ func TestFileService_GetForbidden(t *testing.T) {
|
||||
}
|
||||
|
||||
_, err = svc.Get(ctx, "user2", info.ID)
|
||||
assertAppError(t, err, http.StatusForbidden)
|
||||
assertAppError(t, err, model.KindPermissionDenied)
|
||||
}
|
||||
|
||||
func TestFileService_List(t *testing.T) {
|
||||
@@ -437,7 +436,7 @@ func TestFileService_Delete(t *testing.T) {
|
||||
}
|
||||
|
||||
_, err = svc.Get(ctx, "user1", info.ID)
|
||||
assertAppError(t, err, http.StatusNotFound)
|
||||
assertAppError(t, err, model.KindNotFound)
|
||||
}
|
||||
|
||||
func TestFileService_DeleteNonEmptyDirectory(t *testing.T) {
|
||||
@@ -469,7 +468,7 @@ func TestFileService_DeleteForbidden(t *testing.T) {
|
||||
}
|
||||
|
||||
err = svc.Delete(ctx, "user2", info.ID)
|
||||
assertAppError(t, err, http.StatusForbidden)
|
||||
assertAppError(t, err, model.KindPermissionDenied)
|
||||
}
|
||||
|
||||
func TestFileService_CreateDir(t *testing.T) {
|
||||
@@ -513,7 +512,7 @@ func TestFileService_VerifyParentForbidden(t *testing.T) {
|
||||
}
|
||||
|
||||
_, err = svc.Upload(ctx, "user2", &dir.ID, "stolen.txt", strings.NewReader("data"))
|
||||
assertAppError(t, err, http.StatusForbidden)
|
||||
assertAppError(t, err, model.KindPermissionDenied)
|
||||
}
|
||||
|
||||
func TestFileService_SoftDelete(t *testing.T) {
|
||||
@@ -530,7 +529,7 @@ func TestFileService_SoftDelete(t *testing.T) {
|
||||
}
|
||||
|
||||
_, err = svc.Get(ctx, "user1", info.ID)
|
||||
assertAppError(t, err, http.StatusNotFound)
|
||||
assertAppError(t, err, model.KindNotFound)
|
||||
}
|
||||
|
||||
func TestFileService_SoftDeleteKeepsStorage(t *testing.T) {
|
||||
@@ -590,5 +589,5 @@ func TestFileService_SoftDeleteForbidden(t *testing.T) {
|
||||
}
|
||||
|
||||
err = svc.Delete(ctx, "user2", info.ID)
|
||||
assertAppError(t, err, http.StatusForbidden)
|
||||
assertAppError(t, err, model.KindPermissionDenied)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user