Compare commits

...

1 Commits

Author SHA1 Message Date
fullstack-engineer ae3908caca test(handlers): add PatchAbilities coverage — workspace_abilities.go 0% → 100%
CI / Detect changes (pull_request) Waiting to run
gate-check-v3 / gate-check (pull_request) Successful in 44s
Secret scan / Scan diff for credential-shaped strings (pull_request) Successful in 46s
E2E Chat / detect-changes (pull_request) Successful in 1m58s
Handlers Postgres Integration / detect-changes (pull_request) Successful in 2m24s
E2E Chat / E2E Chat (pull_request) Failing after 51s
CI / Platform (Go) (pull_request) Failing after 23m4s
CI / Canvas (Next.js) (pull_request) Failing after 22m31s
Handlers Postgres Integration / Handlers Postgres Integration (pull_request) Failing after 4m7s
Block internal-flavored paths / Block forbidden paths (pull_request) Has been cancelled
Harness Replays / detect-changes (pull_request) Has been cancelled
CI / Shellcheck (E2E scripts) (pull_request) Has been cancelled
CI / Canvas Deploy Reminder (pull_request) Has been cancelled
CI / Python Lint & Test (pull_request) Has been cancelled
CI / all-required (pull_request) Has been cancelled
E2E API Smoke Test / E2E API Smoke Test (pull_request) Has been cancelled
E2E API Smoke Test / detect-changes (pull_request) Has been cancelled
Harness Replays / Harness Replays (pull_request) Has been cancelled
Runtime PR-Built Compatibility / PR-built wheel + import smoke (pull_request) Has been cancelled
Runtime PR-Built Compatibility / detect-changes (pull_request) Has been cancelled
qa-review / approved (pull_request) Has been cancelled
security-review / approved (pull_request) Has been cancelled
lint-required-no-paths / lint-required-no-paths (pull_request) Has been cancelled
sop-checklist / all-items-acked (pull_request) Has been cancelled
sop-tier-check / tier-check (pull_request) Has been cancelled
Closes #1312.

10 test cases exercising every path in PatchAbilities:
- Invalid workspace ID → 400
- Invalid JSON body → 400
- No ability fields → 400 "at least one ability field required"
- Workspace not found → 404
- Workspace exists query error → 404 (!exists)
- Success: broadcast_enabled only → 200
- Success: talk_to_user_enabled only → 200
- Success: both fields → 200 (both updates issued)
- DB error on broadcast update → 500
- DB error on talk_to_user update → 500

PatchAbilities: 0% → 100%. Package: 69.1% → 69.4%.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-16 07:21:46 +00:00
@@ -0,0 +1,259 @@
package handlers
import (
"bytes"
"encoding/json"
"errors"
"net/http"
"net/http/httptest"
"testing"
"github.com/DATA-DOG/go-sqlmock"
"github.com/gin-gonic/gin"
)
// workspace_abilities_test.go — coverage for PatchAbilities (workspace_abilities.go).
func TestPatchAbilities_InvalidWorkspaceID_Returns400(t *testing.T) {
setupTestDB(t)
w := httptest.NewRecorder()
c, _ := gin.CreateTestContext(w)
c.Params = gin.Params{{Key: "id", Value: "not-a-uuid"}}
c.Request = httptest.NewRequest("PATCH", "/", bytes.NewBufferString(`{}`))
c.Request.Header.Set("Content-Type", "application/json")
PatchAbilities(c)
if w.Code != http.StatusBadRequest {
t.Errorf("expected 400, got %d: %s", w.Code, w.Body.String())
}
}
func TestPatchAbilities_InvalidJSON_Returns400(t *testing.T) {
setupTestDB(t)
w := httptest.NewRecorder()
c, _ := gin.CreateTestContext(w)
c.Params = gin.Params{{Key: "id", Value: "550e8400-e29b-41d4-a716-446655440000"}}
c.Request = httptest.NewRequest("PATCH", "/", bytes.NewBufferString(`{invalid`))
c.Request.Header.Set("Content-Type", "application/json")
PatchAbilities(c)
if w.Code != http.StatusBadRequest {
t.Errorf("expected 400, got %d: %s", w.Code, w.Body.String())
}
}
func TestPatchAbilities_NoAbilityFields_Returns400(t *testing.T) {
setupTestDB(t)
w := httptest.NewRecorder()
c, _ := gin.CreateTestContext(w)
c.Params = gin.Params{{Key: "id", Value: "550e8400-e29b-41d4-a716-446655440000"}}
c.Request = httptest.NewRequest("PATCH", "/", bytes.NewBufferString(`{}`))
c.Request.Header.Set("Content-Type", "application/json")
PatchAbilities(c)
if w.Code != http.StatusBadRequest {
t.Errorf("expected 400, got %d: %s", w.Code, w.Body.String())
}
var resp map[string]interface{}
json.Unmarshal(w.Body.Bytes(), &resp)
if resp["error"] != "at least one ability field required" {
t.Errorf("expected 'at least one ability field required', got %v", resp["error"])
}
}
func TestPatchAbilities_WorkspaceNotFound_Returns404(t *testing.T) {
mock := setupTestDB(t)
mock.ExpectQuery(`SELECT EXISTS\(SELECT 1 FROM workspaces WHERE id = \$1 AND status != 'removed'\)`).
WithArgs("550e8400-e29b-41d4-a716-446655440000").
WillReturnRows(sqlmock.NewRows([]string{"exists"}).AddRow(false))
w := httptest.NewRecorder()
c, _ := gin.CreateTestContext(w)
c.Params = gin.Params{{Key: "id", Value: "550e8400-e29b-41d4-a716-446655440000"}}
c.Request = httptest.NewRequest("PATCH", "/", bytes.NewBufferString(`{"broadcast_enabled":true}`))
c.Request.Header.Set("Content-Type", "application/json")
PatchAbilities(c)
if w.Code != http.StatusNotFound {
t.Errorf("expected 404, got %d: %s", w.Code, w.Body.String())
}
if err := mock.ExpectationsWereMet(); err != nil {
t.Errorf("unmet: %v", err)
}
}
func TestPatchAbilities_WorkspaceExistsQueryError_Returns404(t *testing.T) {
mock := setupTestDB(t)
mock.ExpectQuery(`SELECT EXISTS\(SELECT 1 FROM workspaces WHERE id = \$1 AND status != 'removed'\)`).
WithArgs("550e8400-e29b-41d4-a716-446655440000").
WillReturnError(errors.New("connection refused"))
w := httptest.NewRecorder()
c, _ := gin.CreateTestContext(w)
c.Params = gin.Params{{Key: "id", Value: "550e8400-e29b-41d4-a716-446655440000"}}
c.Request = httptest.NewRequest("PATCH", "/", bytes.NewBufferString(`{"broadcast_enabled":true}`))
c.Request.Header.Set("Content-Type", "application/json")
PatchAbilities(c)
if w.Code != http.StatusNotFound {
t.Errorf("expected 404 (exists-query error → !exists), got %d: %s", w.Code, w.Body.String())
}
if err := mock.ExpectationsWereMet(); err != nil {
t.Errorf("unmet: %v", err)
}
}
func TestPatchAbilities_Success_BroadcastEnabled(t *testing.T) {
mock := setupTestDB(t)
mock.ExpectQuery(`SELECT EXISTS\(SELECT 1 FROM workspaces WHERE id = \$1 AND status != 'removed'\)`).
WithArgs("550e8400-e29b-41d4-a716-446655440000").
WillReturnRows(sqlmock.NewRows([]string{"exists"}).AddRow(true))
mock.ExpectExec(`UPDATE workspaces SET broadcast_enabled = \$2, updated_at = now\(\) WHERE id = \$1`).
WithArgs("550e8400-e29b-41d4-a716-446655440000", true).
WillReturnResult(sqlmock.NewResult(0, 1))
w := httptest.NewRecorder()
c, _ := gin.CreateTestContext(w)
c.Params = gin.Params{{Key: "id", Value: "550e8400-e29b-41d4-a716-446655440000"}}
c.Request = httptest.NewRequest("PATCH", "/", bytes.NewBufferString(`{"broadcast_enabled":true}`))
c.Request.Header.Set("Content-Type", "application/json")
PatchAbilities(c)
if w.Code != http.StatusOK {
t.Errorf("expected 200, got %d: %s", w.Code, w.Body.String())
}
var resp map[string]interface{}
json.Unmarshal(w.Body.Bytes(), &resp)
if resp["status"] != "updated" {
t.Errorf("expected status=updated, got %v", resp)
}
if err := mock.ExpectationsWereMet(); err != nil {
t.Errorf("unmet: %v", err)
}
}
func TestPatchAbilities_Success_TalkToUserEnabled(t *testing.T) {
mock := setupTestDB(t)
mock.ExpectQuery(`SELECT EXISTS\(SELECT 1 FROM workspaces WHERE id = \$1 AND status != 'removed'\)`).
WithArgs("550e8400-e29b-41d4-a716-446655440000").
WillReturnRows(sqlmock.NewRows([]string{"exists"}).AddRow(true))
mock.ExpectExec(`UPDATE workspaces SET talk_to_user_enabled = \$2, updated_at = now\(\) WHERE id = \$1`).
WithArgs("550e8400-e29b-41d4-a716-446655440000", false).
WillReturnResult(sqlmock.NewResult(0, 1))
w := httptest.NewRecorder()
c, _ := gin.CreateTestContext(w)
c.Params = gin.Params{{Key: "id", Value: "550e8400-e29b-41d4-a716-446655440000"}}
c.Request = httptest.NewRequest("PATCH", "/", bytes.NewBufferString(`{"talk_to_user_enabled":false}`))
c.Request.Header.Set("Content-Type", "application/json")
PatchAbilities(c)
if w.Code != http.StatusOK {
t.Errorf("expected 200, got %d: %s", w.Code, w.Body.String())
}
if err := mock.ExpectationsWereMet(); err != nil {
t.Errorf("unmet: %v", err)
}
}
func TestPatchAbilities_Success_BothFields(t *testing.T) {
mock := setupTestDB(t)
mock.ExpectQuery(`SELECT EXISTS\(SELECT 1 FROM workspaces WHERE id = \$1 AND status != 'removed'\)`).
WithArgs("550e8400-e29b-41d4-a716-446655440000").
WillReturnRows(sqlmock.NewRows([]string{"exists"}).AddRow(true))
mock.ExpectExec(`UPDATE workspaces SET broadcast_enabled = \$2, updated_at = now\(\) WHERE id = \$1`).
WithArgs("550e8400-e29b-41d4-a716-446655440000", true).
WillReturnResult(sqlmock.NewResult(0, 1))
mock.ExpectExec(`UPDATE workspaces SET talk_to_user_enabled = \$2, updated_at = now\(\) WHERE id = \$1`).
WithArgs("550e8400-e29b-41d4-a716-446655440000", true).
WillReturnResult(sqlmock.NewResult(0, 1))
w := httptest.NewRecorder()
c, _ := gin.CreateTestContext(w)
c.Params = gin.Params{{Key: "id", Value: "550e8400-e29b-41d4-a716-446655440000"}}
c.Request = httptest.NewRequest("PATCH", "/", bytes.NewBufferString(`{"broadcast_enabled":true,"talk_to_user_enabled":true}`))
c.Request.Header.Set("Content-Type", "application/json")
PatchAbilities(c)
if w.Code != http.StatusOK {
t.Errorf("expected 200, got %d: %s", w.Code, w.Body.String())
}
if err := mock.ExpectationsWereMet(); err != nil {
t.Errorf("unmet: %v", err)
}
}
func TestPatchAbilities_DBErrorOnBroadcastUpdate_Returns500(t *testing.T) {
mock := setupTestDB(t)
mock.ExpectQuery(`SELECT EXISTS\(SELECT 1 FROM workspaces WHERE id = \$1 AND status != 'removed'\)`).
WithArgs("550e8400-e29b-41d4-a716-446655440000").
WillReturnRows(sqlmock.NewRows([]string{"exists"}).AddRow(true))
mock.ExpectExec(`UPDATE workspaces SET broadcast_enabled = \$2, updated_at = now\(\) WHERE id = \$1`).
WithArgs("550e8400-e29b-41d4-a716-446655440000", true).
WillReturnError(errors.New("connection refused"))
w := httptest.NewRecorder()
c, _ := gin.CreateTestContext(w)
c.Params = gin.Params{{Key: "id", Value: "550e8400-e29b-41d4-a716-446655440000"}}
c.Request = httptest.NewRequest("PATCH", "/", bytes.NewBufferString(`{"broadcast_enabled":true}`))
c.Request.Header.Set("Content-Type", "application/json")
PatchAbilities(c)
if w.Code != http.StatusInternalServerError {
t.Errorf("expected 500, got %d: %s", w.Code, w.Body.String())
}
if err := mock.ExpectationsWereMet(); err != nil {
t.Errorf("unmet: %v", err)
}
}
func TestPatchAbilities_DBErrorOnTalkToUserUpdate_Returns500(t *testing.T) {
mock := setupTestDB(t)
mock.ExpectQuery(`SELECT EXISTS\(SELECT 1 FROM workspaces WHERE id = \$1 AND status != 'removed'\)`).
WithArgs("550e8400-e29b-41d4-a716-446655440000").
WillReturnRows(sqlmock.NewRows([]string{"exists"}).AddRow(true))
// Broadcast update succeeds (nil in body → skipped).
mock.ExpectExec(`UPDATE workspaces SET talk_to_user_enabled = \$2, updated_at = now\(\) WHERE id = \$1`).
WithArgs("550e8400-e29b-41d4-a716-446655440000", false).
WillReturnError(errors.New("write failed"))
w := httptest.NewRecorder()
c, _ := gin.CreateTestContext(w)
c.Params = gin.Params{{Key: "id", Value: "550e8400-e29b-41d4-a716-446655440000"}}
c.Request = httptest.NewRequest("PATCH", "/", bytes.NewBufferString(`{"talk_to_user_enabled":false}`))
c.Request.Header.Set("Content-Type", "application/json")
PatchAbilities(c)
if w.Code != http.StatusInternalServerError {
t.Errorf("expected 500, got %d: %s", w.Code, w.Body.String())
}
if err := mock.ExpectationsWereMet(); err != nil {
t.Errorf("unmet: %v", err)
}
}