|
|
|
@@ -0,0 +1,313 @@
|
|
|
|
|
package handlers
|
|
|
|
|
|
|
|
|
|
import (
|
|
|
|
|
"bytes"
|
|
|
|
|
"context"
|
|
|
|
|
"database/sql"
|
|
|
|
|
"encoding/json"
|
|
|
|
|
"errors"
|
|
|
|
|
"net/http"
|
|
|
|
|
"net/http/httptest"
|
|
|
|
|
"testing"
|
|
|
|
|
|
|
|
|
|
"github.com/DATA-DOG/go-sqlmock"
|
|
|
|
|
"github.com/Molecule-AI/molecule-monorepo/platform/internal/db"
|
|
|
|
|
"github.com/gin-gonic/gin"
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
// -------------------------------------------------------------------------- //
|
|
|
|
|
// Helpers
|
|
|
|
|
// -------------------------------------------------------------------------- //
|
|
|
|
|
|
|
|
|
|
func setupAbilitiesTest(t *testing.T) (sqlmock.Sqlmock, func()) {
|
|
|
|
|
t.Helper()
|
|
|
|
|
mockDB, mock, err := sqlmock.New()
|
|
|
|
|
if err != nil {
|
|
|
|
|
t.Fatalf("failed to create sqlmock: %v", err)
|
|
|
|
|
}
|
|
|
|
|
prev := db.DB
|
|
|
|
|
db.DB = mockDB
|
|
|
|
|
return mock, func() {
|
|
|
|
|
db.DB = prev
|
|
|
|
|
mockDB.Close()
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// -------------------------------------------------------------------------- //
|
|
|
|
|
// PatchAbilities
|
|
|
|
|
// -------------------------------------------------------------------------- //
|
|
|
|
|
|
|
|
|
|
func TestPatchAbilities_InvalidWorkspaceID_Returns400(t *testing.T) {
|
|
|
|
|
_, cleanup := setupAbilitiesTest(t)
|
|
|
|
|
defer cleanup()
|
|
|
|
|
|
|
|
|
|
w := httptest.NewRecorder()
|
|
|
|
|
c, _ := gin.CreateTestContext(w)
|
|
|
|
|
c.Params = gin.Params{{Key: "id", Value: "not-a-uuid"}}
|
|
|
|
|
c.Request = httptest.NewRequest("PATCH",
|
|
|
|
|
"/workspaces/not-a-uuid/abilities",
|
|
|
|
|
bytes.NewBufferString(`{"broadcast_enabled":true}`))
|
|
|
|
|
c.Request.Header.Set("Content-Type", "application/json")
|
|
|
|
|
c.Request = c.Request.WithContext(context.Background())
|
|
|
|
|
|
|
|
|
|
PatchAbilities(c)
|
|
|
|
|
|
|
|
|
|
if w.Code != http.StatusBadRequest {
|
|
|
|
|
t.Errorf("expected 400, got %d: %s", w.Code, w.Body.String())
|
|
|
|
|
}
|
|
|
|
|
var body map[string]string
|
|
|
|
|
json.Unmarshal(w.Body.Bytes(), &body)
|
|
|
|
|
if body["error"] != "invalid workspace ID" {
|
|
|
|
|
t.Errorf("expected 'invalid workspace ID', got %q", body["error"])
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func TestPatchAbilities_EmptyBody_Returns400(t *testing.T) {
|
|
|
|
|
_, cleanup := setupAbilitiesTest(t)
|
|
|
|
|
defer cleanup()
|
|
|
|
|
|
|
|
|
|
w := httptest.NewRecorder()
|
|
|
|
|
c, _ := gin.CreateTestContext(w)
|
|
|
|
|
c.Params = gin.Params{{Key: "id", Value: "550e8400-e29b-41d4-a716-446655440000"}}
|
|
|
|
|
c.Request = httptest.NewRequest("PATCH",
|
|
|
|
|
"/workspaces/550e8400-e29b-41d4-a716-446655440000/abilities",
|
|
|
|
|
bytes.NewBufferString(`{}`))
|
|
|
|
|
c.Request.Header.Set("Content-Type", "application/json")
|
|
|
|
|
c.Request = c.Request.WithContext(context.Background())
|
|
|
|
|
|
|
|
|
|
PatchAbilities(c)
|
|
|
|
|
|
|
|
|
|
if w.Code != http.StatusBadRequest {
|
|
|
|
|
t.Errorf("expected 400, got %d: %s", w.Code, w.Body.String())
|
|
|
|
|
}
|
|
|
|
|
var body map[string]string
|
|
|
|
|
json.Unmarshal(w.Body.Bytes(), &body)
|
|
|
|
|
if body["error"] != "at least one ability field required" {
|
|
|
|
|
t.Errorf("expected 'at least one ability field required', got %q", body["error"])
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func TestPatchAbilities_InvalidJSON_Returns400(t *testing.T) {
|
|
|
|
|
_, cleanup := setupAbilitiesTest(t)
|
|
|
|
|
defer cleanup()
|
|
|
|
|
|
|
|
|
|
w := httptest.NewRecorder()
|
|
|
|
|
c, _ := gin.CreateTestContext(w)
|
|
|
|
|
c.Params = gin.Params{{Key: "id", Value: "550e8400-e29b-41d4-a716-446655440000"}}
|
|
|
|
|
c.Request = httptest.NewRequest("PATCH",
|
|
|
|
|
"/workspaces/550e8400-e29b-41d4-a716-446655440000/abilities",
|
|
|
|
|
bytes.NewBufferString(`{invalid}`))
|
|
|
|
|
c.Request.Header.Set("Content-Type", "application/json")
|
|
|
|
|
c.Request = c.Request.WithContext(context.Background())
|
|
|
|
|
|
|
|
|
|
PatchAbilities(c)
|
|
|
|
|
|
|
|
|
|
if w.Code != http.StatusBadRequest {
|
|
|
|
|
t.Errorf("expected 400, got %d: %s", w.Code, w.Body.String())
|
|
|
|
|
}
|
|
|
|
|
var body map[string]string
|
|
|
|
|
json.Unmarshal(w.Body.Bytes(), &body)
|
|
|
|
|
if body["error"] != "invalid request body" {
|
|
|
|
|
t.Errorf("expected 'invalid request body', got %q", body["error"])
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func TestPatchAbilities_WorkspaceNotFound_Returns404(t *testing.T) {
|
|
|
|
|
mock, cleanup := setupAbilitiesTest(t)
|
|
|
|
|
defer cleanup()
|
|
|
|
|
|
|
|
|
|
mock.ExpectQuery("SELECT EXISTS").
|
|
|
|
|
WithArgs("550e8400-e29b-41d4-a716-446655440000").
|
|
|
|
|
WillReturnError(sql.ErrNoRows)
|
|
|
|
|
|
|
|
|
|
w := httptest.NewRecorder()
|
|
|
|
|
c, _ := gin.CreateTestContext(w)
|
|
|
|
|
c.Params = gin.Params{{Key: "id", Value: "550e8400-e29b-41d4-a716-446655440000"}}
|
|
|
|
|
c.Request = httptest.NewRequest("PATCH",
|
|
|
|
|
"/workspaces/550e8400-e29b-41d4-a716-446655440000/abilities",
|
|
|
|
|
bytes.NewBufferString(`{"broadcast_enabled":true}`))
|
|
|
|
|
c.Request.Header.Set("Content-Type", "application/json")
|
|
|
|
|
c.Request = c.Request.WithContext(context.Background())
|
|
|
|
|
|
|
|
|
|
PatchAbilities(c)
|
|
|
|
|
|
|
|
|
|
if w.Code != http.StatusNotFound {
|
|
|
|
|
t.Errorf("expected 404, got %d: %s", w.Code, w.Body.String())
|
|
|
|
|
}
|
|
|
|
|
var body map[string]string
|
|
|
|
|
json.Unmarshal(w.Body.Bytes(), &body)
|
|
|
|
|
if body["error"] != "workspace not found" {
|
|
|
|
|
t.Errorf("expected 'workspace not found', got %q", body["error"])
|
|
|
|
|
}
|
|
|
|
|
if err := mock.ExpectationsWereMet(); err != nil {
|
|
|
|
|
t.Errorf("unmet expectations: %v", err)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func TestPatchAbilities_WorkspaceDBError_Returns404(t *testing.T) {
|
|
|
|
|
mock, cleanup := setupAbilitiesTest(t)
|
|
|
|
|
defer cleanup()
|
|
|
|
|
|
|
|
|
|
mock.ExpectQuery("SELECT EXISTS").
|
|
|
|
|
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",
|
|
|
|
|
"/workspaces/550e8400-e29b-41d4-a716-446655440000/abilities",
|
|
|
|
|
bytes.NewBufferString(`{"broadcast_enabled":true}`))
|
|
|
|
|
c.Request.Header.Set("Content-Type", "application/json")
|
|
|
|
|
c.Request = c.Request.WithContext(context.Background())
|
|
|
|
|
|
|
|
|
|
PatchAbilities(c)
|
|
|
|
|
|
|
|
|
|
// Handler short-circuits on err || !exists, so any DB error → 404.
|
|
|
|
|
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 expectations: %v", err)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func TestPatchAbilities_UpdateBroadcastEnabled_Returns200(t *testing.T) {
|
|
|
|
|
mock, cleanup := setupAbilitiesTest(t)
|
|
|
|
|
defer cleanup()
|
|
|
|
|
|
|
|
|
|
mock.ExpectQuery("SELECT EXISTS").
|
|
|
|
|
WithArgs("550e8400-e29b-41d4-a716-446655440000").
|
|
|
|
|
WillReturnRows(sqlmock.NewRows([]string{"exists"}).AddRow(true))
|
|
|
|
|
// $1=id, $2=value in UPDATE SET col=$2 WHERE id=$1.
|
|
|
|
|
mock.ExpectExec("UPDATE workspaces SET broadcast_enabled").
|
|
|
|
|
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",
|
|
|
|
|
"/workspaces/550e8400-e29b-41d4-a716-446655440000/abilities",
|
|
|
|
|
bytes.NewBufferString(`{"broadcast_enabled":true}`))
|
|
|
|
|
c.Request.Header.Set("Content-Type", "application/json")
|
|
|
|
|
c.Request = c.Request.WithContext(context.Background())
|
|
|
|
|
|
|
|
|
|
PatchAbilities(c)
|
|
|
|
|
|
|
|
|
|
if w.Code != http.StatusOK {
|
|
|
|
|
t.Errorf("expected 200, got %d: %s", w.Code, w.Body.String())
|
|
|
|
|
}
|
|
|
|
|
var body map[string]string
|
|
|
|
|
json.Unmarshal(w.Body.Bytes(), &body)
|
|
|
|
|
if body["status"] != "updated" {
|
|
|
|
|
t.Errorf("expected status=updated, got %v", body)
|
|
|
|
|
}
|
|
|
|
|
if err := mock.ExpectationsWereMet(); err != nil {
|
|
|
|
|
t.Errorf("unmet expectations: %v", err)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func TestPatchAbilities_UpdateTalkToUserEnabled_Returns200(t *testing.T) {
|
|
|
|
|
mock, cleanup := setupAbilitiesTest(t)
|
|
|
|
|
defer cleanup()
|
|
|
|
|
|
|
|
|
|
mock.ExpectQuery("SELECT EXISTS").
|
|
|
|
|
WithArgs("550e8400-e29b-41d4-a716-446655440000").
|
|
|
|
|
WillReturnRows(sqlmock.NewRows([]string{"exists"}).AddRow(true))
|
|
|
|
|
// $1=id, $2=value in UPDATE SET col=$2 WHERE id=$1.
|
|
|
|
|
mock.ExpectExec("UPDATE workspaces SET talk_to_user_enabled").
|
|
|
|
|
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",
|
|
|
|
|
"/workspaces/550e8400-e29b-41d4-a716-446655440000/abilities",
|
|
|
|
|
bytes.NewBufferString(`{"talk_to_user_enabled":true}`))
|
|
|
|
|
c.Request.Header.Set("Content-Type", "application/json")
|
|
|
|
|
c.Request = c.Request.WithContext(context.Background())
|
|
|
|
|
|
|
|
|
|
PatchAbilities(c)
|
|
|
|
|
|
|
|
|
|
if w.Code != http.StatusOK {
|
|
|
|
|
t.Errorf("expected 200, got %d: %s", w.Code, w.Body.String())
|
|
|
|
|
}
|
|
|
|
|
var body map[string]string
|
|
|
|
|
json.Unmarshal(w.Body.Bytes(), &body)
|
|
|
|
|
if body["status"] != "updated" {
|
|
|
|
|
t.Errorf("expected status=updated, got %v", body)
|
|
|
|
|
}
|
|
|
|
|
if err := mock.ExpectationsWereMet(); err != nil {
|
|
|
|
|
t.Errorf("unmet expectations: %v", err)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func TestPatchAbilities_UpdateBothAbilities_Returns200(t *testing.T) {
|
|
|
|
|
mock, cleanup := setupAbilitiesTest(t)
|
|
|
|
|
defer cleanup()
|
|
|
|
|
|
|
|
|
|
mock.ExpectQuery("SELECT EXISTS").
|
|
|
|
|
WithArgs("550e8400-e29b-41d4-a716-446655440000").
|
|
|
|
|
WillReturnRows(sqlmock.NewRows([]string{"exists"}).AddRow(true))
|
|
|
|
|
mock.ExpectExec("UPDATE workspaces SET broadcast_enabled").
|
|
|
|
|
WithArgs("550e8400-e29b-41d4-a716-446655440000", true).
|
|
|
|
|
WillReturnResult(sqlmock.NewResult(0, 1))
|
|
|
|
|
mock.ExpectExec("UPDATE workspaces SET talk_to_user_enabled").
|
|
|
|
|
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",
|
|
|
|
|
"/workspaces/550e8400-e29b-41d4-a716-446655440000/abilities",
|
|
|
|
|
bytes.NewBufferString(`{"broadcast_enabled":true,"talk_to_user_enabled":false}`))
|
|
|
|
|
c.Request.Header.Set("Content-Type", "application/json")
|
|
|
|
|
c.Request = c.Request.WithContext(context.Background())
|
|
|
|
|
|
|
|
|
|
PatchAbilities(c)
|
|
|
|
|
|
|
|
|
|
if w.Code != http.StatusOK {
|
|
|
|
|
t.Errorf("expected 200, got %d: %s", w.Code, w.Body.String())
|
|
|
|
|
}
|
|
|
|
|
var body map[string]string
|
|
|
|
|
json.Unmarshal(w.Body.Bytes(), &body)
|
|
|
|
|
if body["status"] != "updated" {
|
|
|
|
|
t.Errorf("expected status=updated, got %v", body)
|
|
|
|
|
}
|
|
|
|
|
if err := mock.ExpectationsWereMet(); err != nil {
|
|
|
|
|
t.Errorf("unmet expectations: %v", err)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func TestPatchAbilities_UpdateBroadcastDisabled_Returns200(t *testing.T) {
|
|
|
|
|
mock, cleanup := setupAbilitiesTest(t)
|
|
|
|
|
defer cleanup()
|
|
|
|
|
|
|
|
|
|
mock.ExpectQuery("SELECT EXISTS").
|
|
|
|
|
WithArgs("550e8400-e29b-41d4-a716-446655440000").
|
|
|
|
|
WillReturnRows(sqlmock.NewRows([]string{"exists"}).AddRow(true))
|
|
|
|
|
mock.ExpectExec("UPDATE workspaces SET broadcast_enabled").
|
|
|
|
|
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",
|
|
|
|
|
"/workspaces/550e8400-e29b-41d4-a716-446655440000/abilities",
|
|
|
|
|
bytes.NewBufferString(`{"broadcast_enabled":false}`))
|
|
|
|
|
c.Request.Header.Set("Content-Type", "application/json")
|
|
|
|
|
c.Request = c.Request.WithContext(context.Background())
|
|
|
|
|
|
|
|
|
|
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 expectations: %v", err)
|
|
|
|
|
}
|
|
|
|
|
}
|