diff --git a/internal/api/requests.go b/internal/api/requests.go index 8c3e4e20..b5ac28bb 100644 --- a/internal/api/requests.go +++ b/internal/api/requests.go @@ -154,6 +154,32 @@ func (h *handlers) handleCreateRequest(w http.ResponseWriter, r *http.Request) { return } + // Auto-approve flow (#355 / #376 U2.5): when the user has the + // auto_approve_requests flag set by an admin, transition the + // just-created pending row through Approve so it dispatches to + // Lidarr immediately. Failures here (Lidarr disabled, defaults + // missing, network error) leave the row pending — same fallback + // as a manual admin approve that hits the same error path. The + // user gets a 201 with whatever status Approve produced; admin + // can still resolve the row in /admin/requests if it stayed + // pending. + // + // The "actor" id passed to Approve is the user themselves — the + // auto-approval is the user acting under the privilege the admin + // granted them via the toggle. The trail of "admin set the flag" + // already lives in audit_log (ActionAutoApproveToggle from U2). + if user.AutoApproveRequests { + approved, aerr := h.lidarrRequests.Approve(r.Context(), row.ID, user.ID, lidarrrequests.ApproveOverrides{}) + if aerr == nil { + row = approved + } else { + h.logger.Warn("api: auto-approve failed; request stays pending", + "user_id", uuidToString(user.ID), + "request_id", uuidToString(row.ID), + "err", aerr) + } + } + writeJSON(w, http.StatusCreated, requestViewFrom(row)) } diff --git a/internal/api/requests_test.go b/internal/api/requests_test.go index b71ac9d5..c3f95a44 100644 --- a/internal/api/requests_test.go +++ b/internal/api/requests_test.go @@ -85,6 +85,45 @@ func createArtistRequest(t *testing.T, h *handlers, user dbq.User, artistMBID, a return rv } +// TestHandleCreateRequest_AutoApprove_LidarrDisabled_StaysPending verifies +// that a user with auto_approve_requests=true still receives a 201 Created +// when Lidarr is disabled in the test environment — the auto-approve attempt +// fails inside Service.Approve with ErrLidarrDisabled, which the handler +// catches and logs, leaving the row in pending state. This is the U2.5 +// fallback contract: a misconfigured Lidarr must not break request creation. +func TestHandleCreateRequest_AutoApprove_LidarrDisabled_StaysPending(t *testing.T) { + h, pool := testHandlers(t) + resetLidarrState(t, h) + + alice := seedUser(t, pool, "alice", "pw", false) + if _, err := pool.Exec(context.Background(), + "UPDATE users SET auto_approve_requests = true WHERE id = $1", alice.ID); err != nil { + t.Fatalf("set auto_approve: %v", err) + } + // Re-fetch so the User row carried into the request context has the flag. + refreshed, err := dbq.New(pool).GetUserByID(context.Background(), alice.ID) + if err != nil { + t.Fatalf("reload user: %v", err) + } + if !refreshed.AutoApproveRequests { + t.Fatalf("auto_approve flag did not persist — bailing") + } + + body := `{"kind":"artist","lidarr_artist_mbid":"auto-mbid-1","artist_name":"Autoband"}` + w := doCreateRequest(h, refreshed, body) + + if w.Code != http.StatusCreated { + t.Fatalf("status = %d, want 201; body = %s", w.Code, w.Body.String()) + } + var rv requestView + if err := json.Unmarshal(w.Body.Bytes(), &rv); err != nil { + t.Fatalf("decode: %v", err) + } + if rv.Status != "pending" { + t.Errorf("status = %q, want pending (auto-approve falls back when Lidarr disabled)", rv.Status) + } +} + // TestHandleCreateRequest_Artist_HappyPath verifies a valid artist request // returns 201 with kind=artist and status=pending. func TestHandleCreateRequest_Artist_HappyPath(t *testing.T) {