|
|
|
@@ -0,0 +1,408 @@
|
|
|
|
|
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"
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
// -------------------------------------------------------------------------- //
|
|
|
|
|
// broadcastTruncate
|
|
|
|
|
// -------------------------------------------------------------------------- //
|
|
|
|
|
|
|
|
|
|
func TestBroadcastTruncate_ShortString_ReturnsUnmodified(t *testing.T) {
|
|
|
|
|
result := broadcastTruncate("hello", 10)
|
|
|
|
|
if result != "hello" {
|
|
|
|
|
t.Errorf("expected 'hello', got %q", result)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func TestBroadcastTruncate_ExactlyMaxLength_ReturnsUnmodified(t *testing.T) {
|
|
|
|
|
result := broadcastTruncate("hello", 5)
|
|
|
|
|
if result != "hello" {
|
|
|
|
|
t.Errorf("expected 'hello', got %q", result)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func TestBroadcastTruncate_ExceedsMaxLength_TruncatesWithEllipsis(t *testing.T) {
|
|
|
|
|
result := broadcastTruncate("hello world", 5)
|
|
|
|
|
if result != "hello…" {
|
|
|
|
|
t.Errorf("expected 'hello…', got %q", result)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func TestBroadcastTruncate_Unicode_TruncatesAtRuneBoundary(t *testing.T) {
|
|
|
|
|
// "日本語" is 3 runes; truncating at 2 should give 2 runes + ellipsis.
|
|
|
|
|
result := broadcastTruncate("日本語テスト", 2)
|
|
|
|
|
if result != "日本…" {
|
|
|
|
|
t.Errorf("expected '日本…', got %q", result)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// -------------------------------------------------------------------------- //
|
|
|
|
|
// BroadcastHandler
|
|
|
|
|
// -------------------------------------------------------------------------- //
|
|
|
|
|
|
|
|
|
|
func setupBroadcastTest(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()
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func TestBroadcast_InvalidWorkspaceID_Returns400(t *testing.T) {
|
|
|
|
|
_, cleanup := setupBroadcastTest(t)
|
|
|
|
|
defer cleanup()
|
|
|
|
|
|
|
|
|
|
h := NewBroadcastHandler(newTestBroadcaster())
|
|
|
|
|
w := httptest.NewRecorder()
|
|
|
|
|
c, _ := gin.CreateTestContext(w)
|
|
|
|
|
c.Params = gin.Params{{Key: "id", Value: "not-a-uuid"}}
|
|
|
|
|
c.Request = httptest.NewRequest("POST", "/workspaces/not-a-uuid/broadcast",
|
|
|
|
|
bytes.NewBufferString(`{"message":"hello"}`))
|
|
|
|
|
c.Request.Header.Set("Content-Type", "application/json")
|
|
|
|
|
c.Request = c.Request.WithContext(context.Background())
|
|
|
|
|
|
|
|
|
|
h.Broadcast(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 TestBroadcast_MissingMessage_Returns400(t *testing.T) {
|
|
|
|
|
_, cleanup := setupBroadcastTest(t)
|
|
|
|
|
defer cleanup()
|
|
|
|
|
|
|
|
|
|
h := NewBroadcastHandler(newTestBroadcaster())
|
|
|
|
|
w := httptest.NewRecorder()
|
|
|
|
|
c, _ := gin.CreateTestContext(w)
|
|
|
|
|
c.Params = gin.Params{{Key: "id", Value: "550e8400-e29b-41d4-a716-446655440000"}}
|
|
|
|
|
c.Request = httptest.NewRequest("POST",
|
|
|
|
|
"/workspaces/550e8400-e29b-41d4-a716-446655440000/broadcast",
|
|
|
|
|
bytes.NewBufferString(`{}`))
|
|
|
|
|
c.Request.Header.Set("Content-Type", "application/json")
|
|
|
|
|
c.Request = c.Request.WithContext(context.Background())
|
|
|
|
|
|
|
|
|
|
h.Broadcast(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"] != "message is required" {
|
|
|
|
|
t.Errorf("expected 'message is required', got %q", body["error"])
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func TestBroadcast_WorkspaceNotFound_Returns404(t *testing.T) {
|
|
|
|
|
mock, cleanup := setupBroadcastTest(t)
|
|
|
|
|
defer cleanup()
|
|
|
|
|
|
|
|
|
|
mock.ExpectQuery("SELECT name, broadcast_enabled FROM workspaces").
|
|
|
|
|
WithArgs("550e8400-e29b-41d4-a716-446655440000").
|
|
|
|
|
WillReturnError(sql.ErrNoRows)
|
|
|
|
|
|
|
|
|
|
h := NewBroadcastHandler(newTestBroadcaster())
|
|
|
|
|
w := httptest.NewRecorder()
|
|
|
|
|
c, _ := gin.CreateTestContext(w)
|
|
|
|
|
c.Params = gin.Params{{Key: "id", Value: "550e8400-e29b-41d4-a716-446655440000"}}
|
|
|
|
|
c.Request = httptest.NewRequest("POST",
|
|
|
|
|
"/workspaces/550e8400-e29b-41d4-a716-446655440000/broadcast",
|
|
|
|
|
bytes.NewBufferString(`{"message":"hello"}`))
|
|
|
|
|
c.Request.Header.Set("Content-Type", "application/json")
|
|
|
|
|
c.Request = c.Request.WithContext(context.Background())
|
|
|
|
|
|
|
|
|
|
h.Broadcast(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 expectations: %v", err)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func TestBroadcast_BroadcastDisabled_Returns403(t *testing.T) {
|
|
|
|
|
mock, cleanup := setupBroadcastTest(t)
|
|
|
|
|
defer cleanup()
|
|
|
|
|
|
|
|
|
|
mock.ExpectQuery("SELECT name, broadcast_enabled FROM workspaces").
|
|
|
|
|
WithArgs("550e8400-e29b-41d4-a716-446655440000").
|
|
|
|
|
WillReturnRows(sqlmock.NewRows([]string{"name", "broadcast_enabled"}).
|
|
|
|
|
AddRow("test-agent", false))
|
|
|
|
|
|
|
|
|
|
h := NewBroadcastHandler(newTestBroadcaster())
|
|
|
|
|
w := httptest.NewRecorder()
|
|
|
|
|
c, _ := gin.CreateTestContext(w)
|
|
|
|
|
c.Params = gin.Params{{Key: "id", Value: "550e8400-e29b-41d4-a716-446655440000"}}
|
|
|
|
|
c.Request = httptest.NewRequest("POST",
|
|
|
|
|
"/workspaces/550e8400-e29b-41d4-a716-446655440000/broadcast",
|
|
|
|
|
bytes.NewBufferString(`{"message":"hello"}`))
|
|
|
|
|
c.Request.Header.Set("Content-Type", "application/json")
|
|
|
|
|
c.Request = c.Request.WithContext(context.Background())
|
|
|
|
|
|
|
|
|
|
h.Broadcast(c)
|
|
|
|
|
|
|
|
|
|
if w.Code != http.StatusForbidden {
|
|
|
|
|
t.Errorf("expected 403, got %d: %s", w.Code, w.Body.String())
|
|
|
|
|
}
|
|
|
|
|
var body map[string]string
|
|
|
|
|
json.Unmarshal(w.Body.Bytes(), &body)
|
|
|
|
|
if body["error"] != "broadcast_disabled" {
|
|
|
|
|
t.Errorf("expected error='broadcast_disabled', got %v", body)
|
|
|
|
|
}
|
|
|
|
|
if _, ok := body["hint"]; !ok {
|
|
|
|
|
t.Errorf("expected hint field in 403 body, got %v", body)
|
|
|
|
|
}
|
|
|
|
|
if err := mock.ExpectationsWereMet(); err != nil {
|
|
|
|
|
t.Errorf("unmet expectations: %v", err)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func TestBroadcast_RecipientQueryFails_Returns500(t *testing.T) {
|
|
|
|
|
mock, cleanup := setupBroadcastTest(t)
|
|
|
|
|
defer cleanup()
|
|
|
|
|
|
|
|
|
|
mock.ExpectQuery("SELECT name, broadcast_enabled FROM workspaces").
|
|
|
|
|
WithArgs("550e8400-e29b-41d4-a716-446655440000").
|
|
|
|
|
WillReturnRows(sqlmock.NewRows([]string{"name", "broadcast_enabled"}).
|
|
|
|
|
AddRow("test-agent", true))
|
|
|
|
|
mock.ExpectQuery("SELECT id FROM workspaces WHERE status != 'removed' AND id != ").
|
|
|
|
|
WithArgs("550e8400-e29b-41d4-a716-446655440000").
|
|
|
|
|
WillReturnError(errors.New("connection refused"))
|
|
|
|
|
|
|
|
|
|
h := NewBroadcastHandler(newTestBroadcaster())
|
|
|
|
|
w := httptest.NewRecorder()
|
|
|
|
|
c, _ := gin.CreateTestContext(w)
|
|
|
|
|
c.Params = gin.Params{{Key: "id", Value: "550e8400-e29b-41d4-a716-446655440000"}}
|
|
|
|
|
c.Request = httptest.NewRequest("POST",
|
|
|
|
|
"/workspaces/550e8400-e29b-41d4-a716-446655440000/broadcast",
|
|
|
|
|
bytes.NewBufferString(`{"message":"hello"}`))
|
|
|
|
|
c.Request.Header.Set("Content-Type", "application/json")
|
|
|
|
|
c.Request = c.Request.WithContext(context.Background())
|
|
|
|
|
|
|
|
|
|
h.Broadcast(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 expectations: %v", err)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func TestBroadcast_NoRecipients_Returns200(t *testing.T) {
|
|
|
|
|
mock, cleanup := setupBroadcastTest(t)
|
|
|
|
|
defer cleanup()
|
|
|
|
|
|
|
|
|
|
mock.ExpectQuery("SELECT name, broadcast_enabled FROM workspaces").
|
|
|
|
|
WithArgs("550e8400-e29b-41d4-a716-446655440000").
|
|
|
|
|
WillReturnRows(sqlmock.NewRows([]string{"name", "broadcast_enabled"}).
|
|
|
|
|
AddRow("test-agent", true))
|
|
|
|
|
mock.ExpectQuery("SELECT id FROM workspaces WHERE status != 'removed' AND id != ").
|
|
|
|
|
WithArgs("550e8400-e29b-41d4-a716-446655440000").
|
|
|
|
|
WillReturnRows(sqlmock.NewRows([]string{"id"})) // no rows
|
|
|
|
|
// Sender's own broadcast_sent insert.
|
|
|
|
|
mock.ExpectExec("INSERT INTO activity_logs").
|
|
|
|
|
WithArgs("550e8400-e29b-41d4-a716-446655440000", "Broadcast sent to 0 workspace(s)").
|
|
|
|
|
WillReturnResult(sqlmock.NewResult(0, 1))
|
|
|
|
|
|
|
|
|
|
h := NewBroadcastHandler(newTestBroadcaster())
|
|
|
|
|
w := httptest.NewRecorder()
|
|
|
|
|
c, _ := gin.CreateTestContext(w)
|
|
|
|
|
c.Params = gin.Params{{Key: "id", Value: "550e8400-e29b-41d4-a716-446655440000"}}
|
|
|
|
|
c.Request = httptest.NewRequest("POST",
|
|
|
|
|
"/workspaces/550e8400-e29b-41d4-a716-446655440000/broadcast",
|
|
|
|
|
bytes.NewBufferString(`{"message":"hello"}`))
|
|
|
|
|
c.Request.Header.Set("Content-Type", "application/json")
|
|
|
|
|
c.Request = c.Request.WithContext(context.Background())
|
|
|
|
|
|
|
|
|
|
h.Broadcast(c)
|
|
|
|
|
|
|
|
|
|
if w.Code != http.StatusOK {
|
|
|
|
|
t.Errorf("expected 200, got %d: %s", w.Code, w.Body.String())
|
|
|
|
|
}
|
|
|
|
|
var body map[string]interface{}
|
|
|
|
|
json.Unmarshal(w.Body.Bytes(), &body)
|
|
|
|
|
if body["status"] != "sent" {
|
|
|
|
|
t.Errorf("expected status=sent, got %v", body)
|
|
|
|
|
}
|
|
|
|
|
if int(body["delivered"].(float64)) != 0 {
|
|
|
|
|
t.Errorf("expected delivered=0, got %v", body["delivered"])
|
|
|
|
|
}
|
|
|
|
|
if err := mock.ExpectationsWereMet(); err != nil {
|
|
|
|
|
t.Errorf("unmet expectations: %v", err)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func TestBroadcast_DeliversToOneRecipient_Returns200(t *testing.T) {
|
|
|
|
|
mock, cleanup := setupBroadcastTest(t)
|
|
|
|
|
defer cleanup()
|
|
|
|
|
|
|
|
|
|
senderID := "550e8400-e29b-41d4-a716-446655440000"
|
|
|
|
|
recipientID := "660e8400-e29b-41d4-a716-446655440001"
|
|
|
|
|
senderName := "test-agent"
|
|
|
|
|
|
|
|
|
|
mock.ExpectQuery("SELECT name, broadcast_enabled FROM workspaces").
|
|
|
|
|
WithArgs(senderID).
|
|
|
|
|
WillReturnRows(sqlmock.NewRows([]string{"name", "broadcast_enabled"}).
|
|
|
|
|
AddRow(senderName, true))
|
|
|
|
|
mock.ExpectQuery("SELECT id FROM workspaces WHERE status != 'removed' AND id != ").
|
|
|
|
|
WithArgs(senderID).
|
|
|
|
|
WillReturnRows(sqlmock.NewRows([]string{"id"}).AddRow(recipientID))
|
|
|
|
|
// activity_logs insert for recipient.
|
|
|
|
|
mock.ExpectExec("INSERT INTO activity_logs").
|
|
|
|
|
WithArgs(recipientID, senderID, "Broadcast from "+senderName+": hello").
|
|
|
|
|
WillReturnResult(sqlmock.NewResult(0, 1))
|
|
|
|
|
// Sender's broadcast_sent insert.
|
|
|
|
|
mock.ExpectExec("INSERT INTO activity_logs").
|
|
|
|
|
WithArgs(senderID, "Broadcast sent to 1 workspace(s)").
|
|
|
|
|
WillReturnResult(sqlmock.NewResult(0, 1))
|
|
|
|
|
|
|
|
|
|
h := NewBroadcastHandler(newTestBroadcaster())
|
|
|
|
|
w := httptest.NewRecorder()
|
|
|
|
|
c, _ := gin.CreateTestContext(w)
|
|
|
|
|
c.Params = gin.Params{{Key: "id", Value: senderID}}
|
|
|
|
|
c.Request = httptest.NewRequest("POST",
|
|
|
|
|
"/workspaces/"+senderID+"/broadcast",
|
|
|
|
|
bytes.NewBufferString(`{"message":"hello"}`))
|
|
|
|
|
c.Request.Header.Set("Content-Type", "application/json")
|
|
|
|
|
c.Request = c.Request.WithContext(context.Background())
|
|
|
|
|
|
|
|
|
|
h.Broadcast(c)
|
|
|
|
|
|
|
|
|
|
if w.Code != http.StatusOK {
|
|
|
|
|
t.Errorf("expected 200, got %d: %s", w.Code, w.Body.String())
|
|
|
|
|
}
|
|
|
|
|
var body map[string]interface{}
|
|
|
|
|
json.Unmarshal(w.Body.Bytes(), &body)
|
|
|
|
|
if int(body["delivered"].(float64)) != 1 {
|
|
|
|
|
t.Errorf("expected delivered=1, got %v", body["delivered"])
|
|
|
|
|
}
|
|
|
|
|
if err := mock.ExpectationsWereMet(); err != nil {
|
|
|
|
|
t.Errorf("unmet expectations: %v", err)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func TestBroadcast_RecipientInsertFails_Continues_Returns200(t *testing.T) {
|
|
|
|
|
mock, cleanup := setupBroadcastTest(t)
|
|
|
|
|
defer cleanup()
|
|
|
|
|
|
|
|
|
|
senderID := "550e8400-e29b-41d4-a716-446655440000"
|
|
|
|
|
recipientID := "660e8400-e29b-41d4-a716-446655440001"
|
|
|
|
|
senderName := "test-agent"
|
|
|
|
|
|
|
|
|
|
mock.ExpectQuery("SELECT name, broadcast_enabled FROM workspaces").
|
|
|
|
|
WithArgs(senderID).
|
|
|
|
|
WillReturnRows(sqlmock.NewRows([]string{"name", "broadcast_enabled"}).
|
|
|
|
|
AddRow(senderName, true))
|
|
|
|
|
mock.ExpectQuery("SELECT id FROM workspaces WHERE status != 'removed' AND id != ").
|
|
|
|
|
WithArgs(senderID).
|
|
|
|
|
WillReturnRows(sqlmock.NewRows([]string{"id"}).AddRow(recipientID))
|
|
|
|
|
// Recipient insert fails — handler logs and continues.
|
|
|
|
|
mock.ExpectExec("INSERT INTO activity_logs").
|
|
|
|
|
WithArgs(recipientID, senderID, "Broadcast from "+senderName+": hello").
|
|
|
|
|
WillReturnError(errors.New("connection refused"))
|
|
|
|
|
// Sender's broadcast_sent insert still succeeds.
|
|
|
|
|
mock.ExpectExec("INSERT INTO activity_logs").
|
|
|
|
|
WithArgs(senderID, "Broadcast sent to 0 workspace(s)").
|
|
|
|
|
WillReturnResult(sqlmock.NewResult(0, 1))
|
|
|
|
|
|
|
|
|
|
h := NewBroadcastHandler(newTestBroadcaster())
|
|
|
|
|
w := httptest.NewRecorder()
|
|
|
|
|
c, _ := gin.CreateTestContext(w)
|
|
|
|
|
c.Params = gin.Params{{Key: "id", Value: senderID}}
|
|
|
|
|
c.Request = httptest.NewRequest("POST",
|
|
|
|
|
"/workspaces/"+senderID+"/broadcast",
|
|
|
|
|
bytes.NewBufferString(`{"message":"hello"}`))
|
|
|
|
|
c.Request.Header.Set("Content-Type", "application/json")
|
|
|
|
|
c.Request = c.Request.WithContext(context.Background())
|
|
|
|
|
|
|
|
|
|
h.Broadcast(c)
|
|
|
|
|
|
|
|
|
|
// Even though the recipient insert failed, the handler still returns 200
|
|
|
|
|
// with delivered=0 (counted only on success).
|
|
|
|
|
if w.Code != http.StatusOK {
|
|
|
|
|
t.Errorf("expected 200, got %d: %s", w.Code, w.Body.String())
|
|
|
|
|
}
|
|
|
|
|
var body map[string]interface{}
|
|
|
|
|
json.Unmarshal(w.Body.Bytes(), &body)
|
|
|
|
|
if int(body["delivered"].(float64)) != 0 {
|
|
|
|
|
t.Errorf("expected delivered=0 (failed inserts don't count), got %v", body["delivered"])
|
|
|
|
|
}
|
|
|
|
|
if err := mock.ExpectationsWereMet(); err != nil {
|
|
|
|
|
t.Errorf("unmet expectations: %v", err)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func TestBroadcast_SenderLogFails_StillReturns200(t *testing.T) {
|
|
|
|
|
mock, cleanup := setupBroadcastTest(t)
|
|
|
|
|
defer cleanup()
|
|
|
|
|
|
|
|
|
|
senderID := "550e8400-e29b-41d4-a716-446655440000"
|
|
|
|
|
recipientID := "660e8400-e29b-41d4-a716-446655440001"
|
|
|
|
|
senderName := "test-agent"
|
|
|
|
|
|
|
|
|
|
mock.ExpectQuery("SELECT name, broadcast_enabled FROM workspaces").
|
|
|
|
|
WithArgs(senderID).
|
|
|
|
|
WillReturnRows(sqlmock.NewRows([]string{"name", "broadcast_enabled"}).
|
|
|
|
|
AddRow(senderName, true))
|
|
|
|
|
mock.ExpectQuery("SELECT id FROM workspaces WHERE status != 'removed' AND id != ").
|
|
|
|
|
WithArgs(senderID).
|
|
|
|
|
WillReturnRows(sqlmock.NewRows([]string{"id"}).AddRow(recipientID))
|
|
|
|
|
mock.ExpectExec("INSERT INTO activity_logs").
|
|
|
|
|
WithArgs(recipientID, senderID, "Broadcast from "+senderName+": hello").
|
|
|
|
|
WillReturnResult(sqlmock.NewResult(0, 1))
|
|
|
|
|
// Sender's broadcast_sent insert fails — best-effort, no effect on response.
|
|
|
|
|
mock.ExpectExec("INSERT INTO activity_logs").
|
|
|
|
|
WithArgs(senderID, "Broadcast sent to 1 workspace(s)").
|
|
|
|
|
WillReturnError(errors.New("connection refused"))
|
|
|
|
|
|
|
|
|
|
h := NewBroadcastHandler(newTestBroadcaster())
|
|
|
|
|
w := httptest.NewRecorder()
|
|
|
|
|
c, _ := gin.CreateTestContext(w)
|
|
|
|
|
c.Params = gin.Params{{Key: "id", Value: senderID}}
|
|
|
|
|
c.Request = httptest.NewRequest("POST",
|
|
|
|
|
"/workspaces/"+senderID+"/broadcast",
|
|
|
|
|
bytes.NewBufferString(`{"message":"hello"}`))
|
|
|
|
|
c.Request.Header.Set("Content-Type", "application/json")
|
|
|
|
|
c.Request = c.Request.WithContext(context.Background())
|
|
|
|
|
|
|
|
|
|
h.Broadcast(c)
|
|
|
|
|
|
|
|
|
|
// Even though the sender log failed, response is still 200.
|
|
|
|
|
if w.Code != http.StatusOK {
|
|
|
|
|
t.Errorf("expected 200, got %d: %s", w.Code, w.Body.String())
|
|
|
|
|
}
|
|
|
|
|
var body map[string]interface{}
|
|
|
|
|
json.Unmarshal(w.Body.Bytes(), &body)
|
|
|
|
|
if int(body["delivered"].(float64)) != 1 {
|
|
|
|
|
t.Errorf("expected delivered=1, got %v", body["delivered"])
|
|
|
|
|
}
|
|
|
|
|
if err := mock.ExpectationsWereMet(); err != nil {
|
|
|
|
|
t.Errorf("unmet expectations: %v", err)
|
|
|
|
|
}
|
|
|
|
|
}
|