Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 00ceb8b6ae |
@@ -0,0 +1,336 @@
|
||||
package handlers
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"database/sql"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
|
||||
"github.com/DATA-DOG/go-sqlmock"
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
// Suppress unused import warning.
|
||||
var _ = sqlmock.Sqlmock(nil)
|
||||
|
||||
|
||||
// validWSID is a properly-formed UUID used throughout this file.
|
||||
const validWSID = "aabbccdd-eeff-1234-5678-123456789abc"
|
||||
|
||||
// patchAbilitiesRequest builds and executes a PATCH /workspaces/:id/abilities request.
|
||||
func patchAbilitiesRequest(id, body string) *httptest.ResponseRecorder {
|
||||
w := httptest.NewRecorder()
|
||||
c, _ := gin.CreateTestContext(w)
|
||||
c.Params = gin.Params{{Key: "id", Value: id}}
|
||||
c.Request = httptest.NewRequest("PATCH", "/workspaces/"+id+"/abilities",
|
||||
bytes.NewBufferString(body))
|
||||
c.Request.Header.Set("Content-Type", "application/json")
|
||||
PatchAbilities(c)
|
||||
return w
|
||||
}
|
||||
|
||||
// ptrBool returns a pointer to a bool, for constructing AbilitiesPayload in JSON.
|
||||
func ptrBool(b bool) *bool { return &b }
|
||||
|
||||
// ==================== Input validation ====================
|
||||
|
||||
// TestPatchAbilities_InvalidWorkspaceID verifies that a malformed UUID in the path
|
||||
// returns HTTP 400 before any DB call.
|
||||
func TestPatchAbilities_InvalidWorkspaceID(t *testing.T) {
|
||||
mock := setupTestDB(t)
|
||||
_ = mock
|
||||
|
||||
w := patchAbilitiesRequest("not-a-uuid", `{"broadcast_enabled":true}`)
|
||||
|
||||
if w.Code != http.StatusBadRequest {
|
||||
t.Errorf("expected 400, got %d: %s", w.Code, w.Body.String())
|
||||
}
|
||||
if err := mock.ExpectationsWereMet(); err != nil {
|
||||
t.Errorf("sqlmock expectations not met: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
// TestPatchAbilities_InvalidJSON verifies that malformed JSON returns HTTP 400.
|
||||
func TestPatchAbilities_InvalidJSON(t *testing.T) {
|
||||
mock := setupTestDB(t)
|
||||
_ = mock
|
||||
|
||||
w := patchAbilitiesRequest(validWSID, "{broken")
|
||||
|
||||
if w.Code != http.StatusBadRequest {
|
||||
t.Errorf("expected 400, got %d: %s", w.Code, w.Body.String())
|
||||
}
|
||||
if err := mock.ExpectationsWereMet(); err != nil {
|
||||
t.Errorf("sqlmock expectations not met: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
// TestPatchAbilities_EmptyBody verifies that a request with neither field
|
||||
// returns HTTP 400 with the correct error key.
|
||||
func TestPatchAbilities_EmptyBody(t *testing.T) {
|
||||
mock := setupTestDB(t)
|
||||
_ = mock
|
||||
|
||||
w := patchAbilitiesRequest(validWSID, `{}`)
|
||||
|
||||
if w.Code != http.StatusBadRequest {
|
||||
t.Errorf("expected 400, got %d: %s", w.Code, w.Body.String())
|
||||
}
|
||||
if err := mock.ExpectationsWereMet(); err != nil {
|
||||
t.Errorf("sqlmock expectations not met: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
// TestPatchAbilities_OnlyNullFields verifies that explicitly null pointer fields
|
||||
// (Gin's default for missing JSON fields) are treated the same as absent fields
|
||||
// and rejected with 400.
|
||||
func TestPatchAbilities_OnlyNullFields(t *testing.T) {
|
||||
mock := setupTestDB(t)
|
||||
_ = mock
|
||||
|
||||
w := patchAbilitiesRequest(validWSID, `{"broadcast_enabled":null,"talk_to_user_enabled":null}`)
|
||||
|
||||
if w.Code != http.StatusBadRequest {
|
||||
t.Errorf("expected 400, got %d: %s", w.Code, w.Body.String())
|
||||
}
|
||||
}
|
||||
|
||||
// ==================== Workspace not found ====================
|
||||
|
||||
// TestPatchAbilities_WorkspaceNotFound verifies that a workspace not in the DB
|
||||
// returns HTTP 404.
|
||||
func TestPatchAbilities_WorkspaceNotFound(t *testing.T) {
|
||||
mock := setupTestDB(t)
|
||||
|
||||
mock.ExpectQuery(`SELECT EXISTS`).
|
||||
WithArgs(validWSID).
|
||||
WillReturnRows(sqlmock.NewRows([]string{"exists"}).AddRow(false))
|
||||
|
||||
w := patchAbilitiesRequest(validWSID, `{"broadcast_enabled":true}`)
|
||||
|
||||
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("sqlmock expectations not met: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
// TestPatchAbilities_ExistenceQueryError verifies that a DB error on the
|
||||
// existence check returns HTTP 404 (handler maps any error to not-found).
|
||||
func TestPatchAbilities_ExistenceQueryError(t *testing.T) {
|
||||
mock := setupTestDB(t)
|
||||
|
||||
mock.ExpectQuery(`SELECT EXISTS`).
|
||||
WithArgs(validWSID).
|
||||
WillReturnError(sql.ErrNoRows)
|
||||
|
||||
w := patchAbilitiesRequest(validWSID, `{"broadcast_enabled":true}`)
|
||||
|
||||
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("sqlmock expectations not met: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
// ==================== Update failures ====================
|
||||
|
||||
// TestPatchAbilities_BroadcastUpdateFails verifies that a DB error on the
|
||||
// broadcast_enabled UPDATE returns HTTP 500.
|
||||
func TestPatchAbilities_BroadcastUpdateFails(t *testing.T) {
|
||||
mock := setupTestDB(t)
|
||||
|
||||
mock.ExpectQuery(`SELECT EXISTS`).
|
||||
WithArgs(validWSID).
|
||||
WillReturnRows(sqlmock.NewRows([]string{"exists"}).AddRow(true))
|
||||
|
||||
mock.ExpectExec(`UPDATE workspaces SET broadcast_enabled = \$2`).
|
||||
WithArgs(validWSID, true).
|
||||
WillReturnError(sql.ErrConnDone)
|
||||
|
||||
w := patchAbilitiesRequest(validWSID, `{"broadcast_enabled":true}`)
|
||||
|
||||
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("sqlmock expectations not met: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
// TestPatchAbilities_TalkToUserUpdateFails verifies that a DB error on the
|
||||
// talk_to_user_enabled UPDATE returns HTTP 500.
|
||||
func TestPatchAbilities_TalkToUserUpdateFails(t *testing.T) {
|
||||
mock := setupTestDB(t)
|
||||
|
||||
mock.ExpectQuery(`SELECT EXISTS`).
|
||||
WithArgs(validWSID).
|
||||
WillReturnRows(sqlmock.NewRows([]string{"exists"}).AddRow(true))
|
||||
|
||||
mock.ExpectExec(`UPDATE workspaces SET talk_to_user_enabled = \$2`).
|
||||
WithArgs(validWSID, true).
|
||||
WillReturnError(sql.ErrConnDone)
|
||||
|
||||
w := patchAbilitiesRequest(validWSID, `{"talk_to_user_enabled":true}`)
|
||||
|
||||
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("sqlmock expectations not met: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
// ==================== Happy path ====================
|
||||
|
||||
// TestPatchAbilities_BroadcastEnabledTrue verifies that enabling broadcast
|
||||
// returns HTTP 200 and writes the correct UPDATE.
|
||||
func TestPatchAbilities_BroadcastEnabledTrue(t *testing.T) {
|
||||
mock := setupTestDB(t)
|
||||
|
||||
mock.ExpectQuery(`SELECT EXISTS`).
|
||||
WithArgs(validWSID).
|
||||
WillReturnRows(sqlmock.NewRows([]string{"exists"}).AddRow(true))
|
||||
|
||||
mock.ExpectExec(`UPDATE workspaces SET broadcast_enabled = \$2`).
|
||||
WithArgs(validWSID, true).
|
||||
WillReturnResult(sqlmock.NewResult(0, 1))
|
||||
|
||||
w := patchAbilitiesRequest(validWSID, `{"broadcast_enabled":true}`)
|
||||
|
||||
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("sqlmock expectations not met: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
// TestPatchAbilities_BroadcastEnabledFalse verifies that disabling broadcast
|
||||
// returns HTTP 200 and writes the correct UPDATE.
|
||||
func TestPatchAbilities_BroadcastEnabledFalse(t *testing.T) {
|
||||
mock := setupTestDB(t)
|
||||
|
||||
mock.ExpectQuery(`SELECT EXISTS`).
|
||||
WithArgs(validWSID).
|
||||
WillReturnRows(sqlmock.NewRows([]string{"exists"}).AddRow(true))
|
||||
|
||||
mock.ExpectExec(`UPDATE workspaces SET broadcast_enabled = \$2`).
|
||||
WithArgs(validWSID, false).
|
||||
WillReturnResult(sqlmock.NewResult(0, 1))
|
||||
|
||||
w := patchAbilitiesRequest(validWSID, `{"broadcast_enabled":false}`)
|
||||
|
||||
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("sqlmock expectations not met: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
// TestPatchAbilities_TalkToUserEnabledTrue verifies that enabling talk_to_user
|
||||
// returns HTTP 200 and writes the correct UPDATE.
|
||||
func TestPatchAbilities_TalkToUserEnabledTrue(t *testing.T) {
|
||||
mock := setupTestDB(t)
|
||||
|
||||
mock.ExpectQuery(`SELECT EXISTS`).
|
||||
WithArgs(validWSID).
|
||||
WillReturnRows(sqlmock.NewRows([]string{"exists"}).AddRow(true))
|
||||
|
||||
mock.ExpectExec(`UPDATE workspaces SET talk_to_user_enabled = \$2`).
|
||||
WithArgs(validWSID, true).
|
||||
WillReturnResult(sqlmock.NewResult(0, 1))
|
||||
|
||||
w := patchAbilitiesRequest(validWSID, `{"talk_to_user_enabled":true}`)
|
||||
|
||||
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("sqlmock expectations not met: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
// TestPatchAbilities_TalkToUserEnabledFalse verifies that disabling talk_to_user
|
||||
// returns HTTP 200 and writes the correct UPDATE.
|
||||
func TestPatchAbilities_TalkToUserEnabledFalse(t *testing.T) {
|
||||
mock := setupTestDB(t)
|
||||
|
||||
mock.ExpectQuery(`SELECT EXISTS`).
|
||||
WithArgs(validWSID).
|
||||
WillReturnRows(sqlmock.NewRows([]string{"exists"}).AddRow(true))
|
||||
|
||||
mock.ExpectExec(`UPDATE workspaces SET talk_to_user_enabled = \$2`).
|
||||
WithArgs(validWSID, false).
|
||||
WillReturnResult(sqlmock.NewResult(0, 1))
|
||||
|
||||
w := patchAbilitiesRequest(validWSID, `{"talk_to_user_enabled":false}`)
|
||||
|
||||
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("sqlmock expectations not met: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
// TestPatchAbilities_BothFields verifies that setting both ability flags in one
|
||||
// request executes both UPDATEs and returns HTTP 200.
|
||||
func TestPatchAbilities_BothFields(t *testing.T) {
|
||||
mock := setupTestDB(t)
|
||||
|
||||
mock.ExpectQuery(`SELECT EXISTS`).
|
||||
WithArgs(validWSID).
|
||||
WillReturnRows(sqlmock.NewRows([]string{"exists"}).AddRow(true))
|
||||
|
||||
// UPDATE order in handler: broadcast first, then talk_to_user.
|
||||
mock.ExpectExec(`UPDATE workspaces SET broadcast_enabled = \$2`).
|
||||
WithArgs(validWSID, true).
|
||||
WillReturnResult(sqlmock.NewResult(0, 1))
|
||||
|
||||
mock.ExpectExec(`UPDATE workspaces SET talk_to_user_enabled = \$2`).
|
||||
WithArgs(validWSID, false).
|
||||
WillReturnResult(sqlmock.NewResult(0, 1))
|
||||
|
||||
w := patchAbilitiesRequest(validWSID, `{"broadcast_enabled":true,"talk_to_user_enabled":false}`)
|
||||
|
||||
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("sqlmock expectations not met: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
// TestPatchAbilities_ResponseBody verifies the success response shape.
|
||||
func TestPatchAbilities_ResponseBody(t *testing.T) {
|
||||
mock := setupTestDB(t)
|
||||
|
||||
mock.ExpectQuery(`SELECT EXISTS`).
|
||||
WithArgs(validWSID).
|
||||
WillReturnRows(sqlmock.NewRows([]string{"exists"}).AddRow(true))
|
||||
|
||||
mock.ExpectExec(`UPDATE workspaces SET broadcast_enabled = \$2`).
|
||||
WithArgs(validWSID, true).
|
||||
WillReturnResult(sqlmock.NewResult(0, 1))
|
||||
|
||||
w := patchAbilitiesRequest(validWSID, `{"broadcast_enabled":true}`)
|
||||
|
||||
body := w.Body.String()
|
||||
if body == "" {
|
||||
t.Fatal("expected non-empty response body")
|
||||
}
|
||||
if !bytes.Contains([]byte(body), []byte(`"status"`)) {
|
||||
t.Errorf("expected response to contain 'status' key, got: %s", body)
|
||||
}
|
||||
if !bytes.Contains([]byte(body), []byte(`"updated"`)) {
|
||||
t.Errorf("expected status 'updated', got: %s", body)
|
||||
}
|
||||
}
|
||||
|
||||
// Suppress unused import warning.
|
||||
var _ = sqlmock.Sqlmock(nil)
|
||||
Reference in New Issue
Block a user