Files
multica/server/internal/handler/space_resolve_test.go
Naiyuan Qing df00c59239 refactor(spaces): decouple project↔space; drop alignment enforcement
Issue/project/autopilot each declare their own space(s) with no
cross-validation — an issue's space need not belong to its project's
space set. Removes:
- backend: ProjectSpaceAmbiguous error, the EnsureProjectHasSpace side
  effect, the 409 space_reassignments reconcile + renumber, and the
  now-orphaned CountProjectIssuesBySpace / ListIssuesByProjectAndSpace
  queries; ResolveSpace falls back to the workspace default for a
  multi-space project instead of erroring.
- frontend: both SpaceProjectConflictDialog wirings and the project-side
  ProjectSpaceReassignDialog, plus the conflict schema/types/i18n.

Data structures unchanged (project_space, issue.space_id,
autopilot.space_id); multi-space projects stay, managed inline via
SpaceMultiPicker on the project detail.

Also bundled from this branch's working tree:
- create-space: derive the key from the name, no silent "T" prefix
- space detail: normal-colour description, shadcn members trigger
- invitations: space_ids so invitees join chosen spaces on accept
  (migration 149)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-09 16:00:49 +08:00

53 lines
1.5 KiB
Go

package handler
import (
"errors"
"net/http/httptest"
"testing"
"github.com/multica-ai/multica/server/internal/service"
)
// TestSpaceResolveMessage locks the single unified wording each service
// space-resolution error maps to. Unrecognized errors return "".
func TestSpaceResolveMessage(t *testing.T) {
cases := []struct {
name string
err error
want string
}{
{"not found", service.ErrSpaceNotFound, "space not found in this workspace"},
{"archived", service.ErrSpaceArchived, "space is archived"},
{"unrelated", errors.New("boom"), ""},
{"nil", nil, ""},
}
for _, tc := range cases {
t.Run(tc.name, func(t *testing.T) {
if got := spaceResolveMessage(tc.err); got != tc.want {
t.Fatalf("spaceResolveMessage(%v) = %q, want %q", tc.err, got, tc.want)
}
})
}
}
// TestWriteSpaceResolveError verifies the writer emits a 400 for space-resolution
// errors and reports false (writing nothing) for anything else so callers can
// fall through to their own handling.
func TestWriteSpaceResolveError(t *testing.T) {
rec := httptest.NewRecorder()
if !writeSpaceResolveError(rec, service.ErrSpaceNotFound) {
t.Fatal("expected writeSpaceResolveError to handle ErrSpaceNotFound")
}
if rec.Code != 400 {
t.Fatalf("expected 400, got %d", rec.Code)
}
rec = httptest.NewRecorder()
if writeSpaceResolveError(rec, errors.New("boom")) {
t.Fatal("expected writeSpaceResolveError to ignore a non-space-resolution error")
}
if rec.Code != 200 {
t.Fatalf("expected nothing written (default 200), got %d", rec.Code)
}
}