refactor(api): merge review-queue into /api/activities, rename resolve to assign

Removes the unused GET /api/activities list/detail endpoints, moves the
review-queue list and resolve/unlock/unassign actions under
/api/activities, renames resolve to assign end-to-end, and drops the
now-unused store.ReviewQueue helper.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
2026-08-04 00:04:03 +02:00
parent eb24dcd89d
commit a394bbb770
10 changed files with 297 additions and 521 deletions

View File

@@ -43,7 +43,7 @@ func doJSONAs(t *testing.T, handler http.Handler, sub, method, path string, body
return rec
}
func TestIsolation_ActivityDetailNotVisibleToOtherUser(t *testing.T) {
func TestIsolation_AssignCannotTargetOtherUsersActivity(t *testing.T) {
s, db, _ := newTestServer(t) // provisions "test-user" (userA)
userB, err := db.ProvisionUser(newCtx(), "user-b", "B")
if err != nil {
@@ -58,21 +58,26 @@ func TestIsolation_ActivityDetailNotVisibleToOtherUser(t *testing.T) {
if err != nil {
t.Fatalf("UpsertActivity: %v", err)
}
_ = userB
router := s.Router()
// userA (the default doJSON identity) can see it.
rec := doJSON(t, router, http.MethodGet, "/api/activities/"+itoa(activityID), nil)
if rec.Code != http.StatusOK {
t.Fatalf("userA GetActivity status = %d, body = %s", rec.Code, rec.Body.String())
kindsB, err := db.ListWorkoutKinds(newCtx(), userB, false)
if err != nil || len(kindsB) == 0 {
t.Fatalf("ListWorkoutKinds(b): len=%d err=%v", len(kindsB), err)
}
kindB := kindsB[0]
if kindB.Name == "Race" { // Race is rejected before the ownership check
kindB = kindsB[1]
}
// userB, given the exact same activity id, gets 404 -- not another
// user's data, and not a 500 that would leak existence either way.
rec = doJSONAs(t, router, "user-b", http.MethodGet, "/api/activities/"+itoa(activityID), nil)
if rec.Code != http.StatusNotFound {
t.Fatalf("userB GetActivity(userA's id) status = %d, want 404, body = %s", rec.Code, rec.Body.String())
// userB, given userA's real activity id (and a kind userB legitimately
// owns), must not be able to write an assignment onto it.
rec := doJSONAs(t, s.Router(), "user-b", http.MethodPost, "/api/activities/"+itoa(activityID)+"/assign", map[string]any{"workout_kind_id": kindB.ID})
if rec.Code == http.StatusOK {
t.Fatalf("userB assigning userA's activity succeeded (status %d), body = %s", rec.Code, rec.Body.String())
}
if _, ok, err := db.CurrentAssignment(newCtx(), userA.ID, activityID); err != nil {
t.Fatalf("CurrentAssignment: %v", err)
} else if ok {
t.Fatalf("userB's rejected assign still created an assignment on userA's activity")
}
}
@@ -106,7 +111,7 @@ func TestIsolation_WorkoutKindUpdateCannotTargetOtherUsersKind(t *testing.T) {
}
}
func TestIsolation_ReviewQueueOnlyShowsOwnActivities(t *testing.T) {
func TestIsolation_ActivitiesListOnlyShowsOwnActivities(t *testing.T) {
s, db, _ := newTestServer(t) // provisions "test-user" (userA)
userB, err := db.ProvisionUser(newCtx(), "user-b", "B")
if err != nil {
@@ -125,7 +130,7 @@ func TestIsolation_ReviewQueueOnlyShowsOwnActivities(t *testing.T) {
t.Fatalf("InsertKindAssignment(b): %v", err)
}
rec := doJSON(t, s.Router(), http.MethodGet, "/api/review-queue/", nil) // as userA
rec := doJSON(t, s.Router(), http.MethodGet, "/api/activities/", nil) // as userA
if rec.Code != http.StatusOK {
t.Fatalf("status = %d, body = %s", rec.Code, rec.Body.String())
}
@@ -135,7 +140,7 @@ func TestIsolation_ReviewQueueOnlyShowsOwnActivities(t *testing.T) {
}
json.Unmarshal(rec.Body.Bytes(), &page)
if page.Total != 0 || len(page.Items) != 0 {
t.Fatalf("expected userA's review queue to be empty (the only assignment belongs to userB), got total=%d items=%d", page.Total, len(page.Items))
t.Fatalf("expected userA's activities list to be empty (the only assignment belongs to userB), got total=%d items=%d", page.Total, len(page.Items))
}
}