package widget import ( "bytes" "encoding/json" "fmt" "net/http" "net/http/httptest" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "gorm.io/gorm" "github.com/gochat/gochat/internal/model" ) // ---------- widgetConversationPayload (via GetConversations /widget/conversations) ---------- func TestWidgetHandler_Cov3_GetConversationsEmptyList_Cov3(t *testing.T) { db, router, _ := setupWidgetHandlerTest(t) seedWidgetHandlerData(t, db) wConfig := httptest.NewRecorder() reqConfig, _ := http.NewRequest("POST", "/api/v1/widget/config?website_token=handler_ws_token_123", nil) router.ServeHTTP(wConfig, reqConfig) require.Equal(t, http.StatusOK, wConfig.Code) var configResp map[string]interface{} require.NoError(t, json.Unmarshal(wConfig.Body.Bytes(), &configResp)) authToken := configResp["contact"].(map[string]interface{})["pubsub_token"].(string) w := httptest.NewRecorder() req, _ := http.NewRequest("GET", "/widget/conversations", nil) req.Header.Set("X-Auth-Token", authToken) router.ServeHTTP(w, req) require.Equal(t, http.StatusOK, w.Code) var resp map[string]interface{} require.NoError(t, json.Unmarshal(w.Body.Bytes(), &resp)) convs, ok := resp["conversations"].([]interface{}) assert.True(t, ok, "should have conversations array") assert.Empty(t, convs) } func TestWidgetHandler_Cov3_GetConversationsReturnsPayload_Cov3(t *testing.T) { db, router, _ := setupWidgetHandlerTest(t) _, inbox := seedWidgetHandlerData(t, db) wConfig := httptest.NewRecorder() reqConfig, _ := http.NewRequest("POST", "/api/v1/widget/config?website_token=handler_ws_token_123", nil) router.ServeHTTP(wConfig, reqConfig) require.Equal(t, http.StatusOK, wConfig.Code) var configResp map[string]interface{} require.NoError(t, json.Unmarshal(wConfig.Body.Bytes(), &configResp)) authToken := configResp["contact"].(map[string]interface{})["pubsub_token"].(string) messageBody, _ := json.Marshal(map[string]interface{}{"message": map[string]interface{}{"content": "hello world"}}) wMsg := httptest.NewRecorder() reqMsg, _ := http.NewRequest("POST", "/api/v1/widget/messages?cw_conversation="+authToken, bytes.NewReader(messageBody)) reqMsg.Header.Set("Content-Type", "application/json") router.ServeHTTP(wMsg, reqMsg) require.Equal(t, http.StatusOK, wMsg.Code, wMsg.Body.String()) w := httptest.NewRecorder() req, _ := http.NewRequest("GET", "/widget/conversations", nil) req.Header.Set("X-Auth-Token", authToken) router.ServeHTTP(w, req) require.Equal(t, http.StatusOK, w.Code) var resp map[string]interface{} require.NoError(t, json.Unmarshal(w.Body.Bytes(), &resp)) convs, ok := resp["conversations"].([]interface{}) assert.True(t, ok, "should have conversations array") require.Len(t, convs, 1) convMap := convs[0].(map[string]interface{}) assert.NotNil(t, convMap["id"]) assert.NotNil(t, convMap["uuid"]) assert.Equal(t, float64(inbox.ID), convMap["inbox_id"]) assert.NotNil(t, convMap["status"]) assert.NotNil(t, convMap["custom_attributes"]) } func TestWidgetHandler_Cov3_GetConversationsNoToken_Cov3(t *testing.T) { _, router, _ := setupWidgetHandlerTest(t) w := httptest.NewRecorder() req, _ := http.NewRequest("GET", "/widget/conversations", nil) router.ServeHTTP(w, req) assert.Equal(t, http.StatusUnauthorized, w.Code) } func TestWidgetHandler_Cov3_GetConversationsInvalidToken_Cov3(t *testing.T) { _, router, _ := setupWidgetHandlerTest(t) w := httptest.NewRecorder() req, _ := http.NewRequest("GET", "/widget/conversations?widget_token=invalid", nil) router.ServeHTTP(w, req) // Invalid token returns 400 (generic error, not "invalid widget_token") assert.True(t, w.Code == http.StatusUnauthorized || w.Code == http.StatusBadRequest, "expected 401 or 400, got %d", w.Code) } // ---------- CreateConversation ---------- func TestWidgetHandler_Cov3_CreateConversation_NoToken_Cov3(t *testing.T) { _, router, _ := setupWidgetHandlerTest(t) w := httptest.NewRecorder() body, _ := json.Marshal(map[string]interface{}{"message": map[string]interface{}{"content": "hi"}}) req, _ := http.NewRequest("POST", "/api/v1/widget/conversations", bytes.NewReader(body)) req.Header.Set("Content-Type", "application/json") router.ServeHTTP(w, req) assert.Equal(t, http.StatusUnauthorized, w.Code) } func TestWidgetHandler_Cov3_CreateConversation_InvalidBody_Cov3(t *testing.T) { _, router, _ := setupWidgetHandlerTest(t) w := httptest.NewRecorder() req, _ := http.NewRequest("POST", "/api/v1/widget/conversations", bytes.NewReader([]byte("not json"))) req.Header.Set("Content-Type", "application/json") req.Header.Set("X-Auth-Token", "some-token") router.ServeHTTP(w, req) assert.Equal(t, http.StatusBadRequest, w.Code) } func TestWidgetHandler_Cov3_CreateConversation_InvalidToken_Cov3(t *testing.T) { _, router, _ := setupWidgetHandlerTest(t) w := httptest.NewRecorder() body, _ := json.Marshal(map[string]interface{}{"message": map[string]interface{}{"content": "hi"}}) req, _ := http.NewRequest("POST", "/api/v1/widget/conversations?cw_conversation=invalid-token", bytes.NewReader(body)) req.Header.Set("Content-Type", "application/json") router.ServeHTTP(w, req) assert.Equal(t, http.StatusBadRequest, w.Code) } func TestWidgetHandler_Cov3_CreateConversation_Success_Cov3(t *testing.T) { db, router, _ := setupWidgetHandlerTest(t) _, inbox := seedWidgetHandlerData(t, db) wConfig := httptest.NewRecorder() reqConfig, _ := http.NewRequest("POST", "/api/v1/widget/config?website_token=handler_ws_token_123", nil) router.ServeHTTP(wConfig, reqConfig) require.Equal(t, http.StatusOK, wConfig.Code) var configResp map[string]interface{} require.NoError(t, json.Unmarshal(wConfig.Body.Bytes(), &configResp)) authToken := configResp["contact"].(map[string]interface{})["pubsub_token"].(string) w := httptest.NewRecorder() body, _ := json.Marshal(map[string]interface{}{"message": map[string]interface{}{"content": "new conversation message"}}) req, _ := http.NewRequest("POST", "/api/v1/widget/conversations?cw_conversation="+authToken, bytes.NewReader(body)) req.Header.Set("Content-Type", "application/json") router.ServeHTTP(w, req) require.Equal(t, http.StatusOK, w.Code, w.Body.String()) var resp map[string]interface{} require.NoError(t, json.Unmarshal(w.Body.Bytes(), &resp)) assert.NotNil(t, resp["id"]) msgs, ok := resp["messages"].([]interface{}) assert.True(t, ok, "should have messages array") if len(msgs) > 0 { msg := msgs[0].(map[string]interface{}) assert.Equal(t, "new conversation message", msg["content"]) } var conv model.Conversation require.NoError(t, db.Where("inbox_id = ?", inbox.ID).First(&conv).Error) } // ---------- PublicGetContact ---------- func TestWidgetHandler_Cov3_PublicGetContact_Success_Cov3(t *testing.T) { db, router, _ := setupWidgetHandlerTest(t) _, inbox, channelAPI := seedPublicAPIInbox(t, db) createBody, _ := json.Marshal(map[string]interface{}{ "name": "Test Contact", "email": "test@example.com", "phone_number": "+1234567890", }) wCreate := httptest.NewRecorder() reqCreate, _ := http.NewRequest("POST", "/public/api/v1/inboxes/"+channelAPI.Identifier+"/contacts", bytes.NewReader(createBody)) reqCreate.Header.Set("Content-Type", "application/json") router.ServeHTTP(wCreate, reqCreate) require.Equal(t, http.StatusOK, wCreate.Code, wCreate.Body.String()) var createResp map[string]interface{} require.NoError(t, json.Unmarshal(wCreate.Body.Bytes(), &createResp)) sourceID := createResp["source_id"].(string) w := httptest.NewRecorder() req, _ := http.NewRequest("GET", "/public/api/v1/inboxes/"+channelAPI.Identifier+"/contacts/"+sourceID, nil) router.ServeHTTP(w, req) require.Equal(t, http.StatusOK, w.Code, w.Body.String()) var resp map[string]interface{} require.NoError(t, json.Unmarshal(w.Body.Bytes(), &resp)) assert.Equal(t, sourceID, resp["source_id"]) assert.Equal(t, "Test Contact", resp["name"]) assert.Equal(t, "test@example.com", resp["email"]) assert.Equal(t, "+1234567890", resp["phone_number"]) assert.NotNil(t, resp["id"]) assert.NotNil(t, resp["pubsub_token"]) _ = inbox } func TestWidgetHandler_Cov3_PublicGetContact_NotFound_Cov3(t *testing.T) { db, router, _ := setupWidgetHandlerTest(t) _, _, channelAPI := seedPublicAPIInbox(t, db) w := httptest.NewRecorder() req, _ := http.NewRequest("GET", "/public/api/v1/inboxes/"+channelAPI.Identifier+"/contacts/nonexistent-source-id", nil) router.ServeHTTP(w, req) assert.True(t, w.Code == http.StatusNotFound || w.Code == http.StatusBadRequest, "expected 404 or 400, got %d: %s", w.Code, w.Body.String()) } func TestWidgetHandler_Cov3_PublicGetContact_InvalidInbox_Cov3(t *testing.T) { _, router, _ := setupWidgetHandlerTest(t) w := httptest.NewRecorder() req, _ := http.NewRequest("GET", "/public/api/v1/inboxes/nonexistent-inbox/contacts/some-id", nil) router.ServeHTTP(w, req) assert.True(t, w.Code == http.StatusNotFound || w.Code == http.StatusBadRequest, "expected 404 or 400, got %d: %s", w.Code, w.Body.String()) } // ---------- uintFromPayload unit test ---------- func TestWidgetHandler_Cov3_UintFromPayload_Cov3(t *testing.T) { assert.Equal(t, uint(42), uintFromPayload(uint(42))) assert.Equal(t, uint(42), uintFromPayload(int(42))) assert.Equal(t, uint(0), uintFromPayload(int(-1))) assert.Equal(t, uint(42), uintFromPayload(int64(42))) assert.Equal(t, uint(0), uintFromPayload(int64(-1))) assert.Equal(t, uint(42), uintFromPayload(float64(42))) assert.Equal(t, uint(0), uintFromPayload(float64(-1))) assert.Equal(t, uint(0), uintFromPayload("not a number")) assert.Equal(t, uint(0), uintFromPayload(nil)) assert.Equal(t, uint(0), uintFromPayload(uint(0))) } // ---------- GetCableToken ---------- func TestWidgetHandler_Cov3_GetCableToken_NoToken_Cov3(t *testing.T) { _, router, _ := setupWidgetHandlerTest(t) w := httptest.NewRecorder() req, _ := http.NewRequest("GET", "/widget/cable_token", nil) router.ServeHTTP(w, req) assert.Equal(t, http.StatusUnauthorized, w.Code) } func TestWidgetHandler_Cov3_GetCableToken_InvalidToken_Cov3(t *testing.T) { _, router, _ := setupWidgetHandlerTest(t) w := httptest.NewRecorder() req, _ := http.NewRequest("GET", "/widget/cable_token?widget_token=invalid", nil) router.ServeHTTP(w, req) assert.True(t, w.Code == http.StatusUnauthorized || w.Code == http.StatusBadRequest, "expected 401 or 400, got %d", w.Code) } func TestWidgetHandler_Cov3_GetCableToken_Success_Cov3(t *testing.T) { db, router, _ := setupWidgetHandlerTest(t) seedWidgetHandlerData(t, db) wConfig := httptest.NewRecorder() reqConfig, _ := http.NewRequest("POST", "/api/v1/widget/config?website_token=handler_ws_token_123", nil) router.ServeHTTP(wConfig, reqConfig) require.Equal(t, http.StatusOK, wConfig.Code) var configResp map[string]interface{} require.NoError(t, json.Unmarshal(wConfig.Body.Bytes(), &configResp)) authToken := configResp["contact"].(map[string]interface{})["pubsub_token"].(string) w := httptest.NewRecorder() req, _ := http.NewRequest("GET", "/widget/cable_token?widget_token="+authToken, nil) router.ServeHTTP(w, req) require.Equal(t, http.StatusOK, w.Code, w.Body.String()) } // ---------- GetContact no token (chatwoot route → 404) ---------- func TestWidgetHandler_Cov3_GetContact_NoToken_ChatwootRoute_Cov3(t *testing.T) { _, router, _ := setupWidgetHandlerTest(t) w := httptest.NewRecorder() req, _ := http.NewRequest("GET", "/api/v1/widget/contact", nil) router.ServeHTTP(w, req) assert.Equal(t, http.StatusNotFound, w.Code) } func TestWidgetHandler_Cov3_GetContact_NoToken_WidgetRoute_Cov3(t *testing.T) { _, router, _ := setupWidgetHandlerTest(t) w := httptest.NewRecorder() req, _ := http.NewRequest("GET", "/widget/contact", nil) router.ServeHTTP(w, req) // /widget/contact is not a registered route — 404 from router assert.Equal(t, http.StatusNotFound, w.Code) } func TestWidgetHandler_Cov3_GetContact_Success_Cov3(t *testing.T) { db, router, _ := setupWidgetHandlerTest(t) seedWidgetHandlerData(t, db) wConfig := httptest.NewRecorder() reqConfig, _ := http.NewRequest("POST", "/api/v1/widget/config?website_token=handler_ws_token_123", nil) router.ServeHTTP(wConfig, reqConfig) require.Equal(t, http.StatusOK, wConfig.Code) var configResp map[string]interface{} require.NoError(t, json.Unmarshal(wConfig.Body.Bytes(), &configResp)) authToken := configResp["contact"].(map[string]interface{})["pubsub_token"].(string) w := httptest.NewRecorder() req, _ := http.NewRequest("GET", "/api/v1/widget/contact?cw_conversation="+authToken, nil) router.ServeHTTP(w, req) require.Equal(t, http.StatusOK, w.Code, w.Body.String()) var resp map[string]interface{} require.NoError(t, json.Unmarshal(w.Body.Bytes(), &resp)) assert.NotNil(t, resp["id"]) } // ---------- UpdateContact no token (chatwoot route → 404) ---------- func TestWidgetHandler_Cov3_UpdateContact_NoToken_ChatwootRoute_Cov3(t *testing.T) { _, router, _ := setupWidgetHandlerTest(t) body, _ := json.Marshal(map[string]interface{}{"name": "Test"}) w := httptest.NewRecorder() req, _ := http.NewRequest("PATCH", "/api/v1/widget/contact", bytes.NewReader(body)) req.Header.Set("Content-Type", "application/json") router.ServeHTTP(w, req) assert.Equal(t, http.StatusNotFound, w.Code) } func TestWidgetHandler_Cov3_UpdateContact_Success_Cov3(t *testing.T) { db, router, _ := setupWidgetHandlerTest(t) seedWidgetHandlerData(t, db) wConfig := httptest.NewRecorder() reqConfig, _ := http.NewRequest("POST", "/api/v1/widget/config?website_token=handler_ws_token_123", nil) router.ServeHTTP(wConfig, reqConfig) require.Equal(t, http.StatusOK, wConfig.Code) var configResp map[string]interface{} require.NoError(t, json.Unmarshal(wConfig.Body.Bytes(), &configResp)) authToken := configResp["contact"].(map[string]interface{})["pubsub_token"].(string) body, _ := json.Marshal(map[string]interface{}{"name": "Updated Name"}) w := httptest.NewRecorder() req, _ := http.NewRequest("PATCH", "/api/v1/widget/contact?cw_conversation="+authToken, bytes.NewReader(body)) req.Header.Set("Content-Type", "application/json") router.ServeHTTP(w, req) require.Equal(t, http.StatusOK, w.Code, w.Body.String()) var resp map[string]interface{} require.NoError(t, json.Unmarshal(w.Body.Bytes(), &resp)) assert.NotNil(t, resp["id"]) assert.Equal(t, true, resp["has_name"]) } // ---------- UpdateContact invalid body ---------- func TestWidgetHandler_Cov3_UpdateContact_InvalidBody_Cov3(t *testing.T) { _, router, _ := setupWidgetHandlerTest(t) w := httptest.NewRecorder() req, _ := http.NewRequest("PATCH", "/api/v1/widget/contact?cw_conversation=some-token", bytes.NewReader([]byte("not json"))) req.Header.Set("Content-Type", "application/json") router.ServeHTTP(w, req) assert.Equal(t, http.StatusBadRequest, w.Code) } // ---------- DestroyContactCustomAttributes ---------- func TestWidgetHandler_Cov3_DestroyContactAttrs_NoToken_ChatwootRoute_Cov3(t *testing.T) { _, router, _ := setupWidgetHandlerTest(t) body, _ := json.Marshal(map[string]interface{}{"custom_attributes": []string{"key1"}}) w := httptest.NewRecorder() req, _ := http.NewRequest("POST", "/api/v1/widget/destroy_custom_attributes", bytes.NewReader(body)) req.Header.Set("Content-Type", "application/json") router.ServeHTTP(w, req) assert.Equal(t, http.StatusNotFound, w.Code) } func TestWidgetHandler_Cov3_DestroyContactAttrs_InvalidBody_Cov3(t *testing.T) { _, router, _ := setupWidgetHandlerTest(t) w := httptest.NewRecorder() req, _ := http.NewRequest("POST", "/api/v1/widget/destroy_custom_attributes?cw_conversation=some-token", bytes.NewReader([]byte("not json"))) req.Header.Set("Content-Type", "application/json") router.ServeHTTP(w, req) assert.Equal(t, http.StatusBadRequest, w.Code) } // ---------- SetUser ---------- func TestWidgetHandler_Cov3_SetUser_NoToken_Cov3(t *testing.T) { _, router, _ := setupWidgetHandlerTest(t) body, _ := json.Marshal(map[string]interface{}{"identifier": "test"}) w := httptest.NewRecorder() req, _ := http.NewRequest("PATCH", "/api/v1/widget/contact/set_user", bytes.NewReader(body)) req.Header.Set("Content-Type", "application/json") router.ServeHTTP(w, req) assert.Equal(t, http.StatusUnauthorized, w.Code) } func TestWidgetHandler_Cov3_SetUser_InvalidBody_Cov3(t *testing.T) { _, router, _ := setupWidgetHandlerTest(t) w := httptest.NewRecorder() req, _ := http.NewRequest("PATCH", "/api/v1/widget/contact/set_user?cw_conversation=some-token", bytes.NewReader([]byte("not json"))) req.Header.Set("Content-Type", "application/json") router.ServeHTTP(w, req) assert.Equal(t, http.StatusBadRequest, w.Code) } // ---------- ToggleTyping ---------- func TestWidgetHandler_Cov3_ToggleTyping_NoToken_Cov3(t *testing.T) { _, router, _ := setupWidgetHandlerTest(t) w := httptest.NewRecorder() req, _ := http.NewRequest("POST", "/widget/conversations/1/toggle_typing", nil) router.ServeHTTP(w, req) assert.Equal(t, http.StatusUnauthorized, w.Code) } func TestWidgetHandler_Cov3_ToggleTyping_InvalidID_Cov3(t *testing.T) { _, router, _ := setupWidgetHandlerTest(t) w := httptest.NewRecorder() req, _ := http.NewRequest("POST", "/widget/conversations/abc/toggle_typing?cw_conversation=some-token", nil) router.ServeHTTP(w, req) assert.Equal(t, http.StatusBadRequest, w.Code) } func TestWidgetHandler_Cov3_ToggleTyping_Success_Cov3(t *testing.T) { db, router, _ := setupWidgetHandlerTest(t) seedWidgetHandlerData(t, db) wConfig := httptest.NewRecorder() reqConfig, _ := http.NewRequest("POST", "/api/v1/widget/config?website_token=handler_ws_token_123", nil) router.ServeHTTP(wConfig, reqConfig) require.Equal(t, http.StatusOK, wConfig.Code) var configResp map[string]interface{} require.NoError(t, json.Unmarshal(wConfig.Body.Bytes(), &configResp)) authToken := configResp["contact"].(map[string]interface{})["pubsub_token"].(string) // Send message to create conversation messageBody, _ := json.Marshal(map[string]interface{}{"message": map[string]interface{}{"content": "hello"}}) wMsg := httptest.NewRecorder() reqMsg, _ := http.NewRequest("POST", "/api/v1/widget/messages?cw_conversation="+authToken, bytes.NewReader(messageBody)) reqMsg.Header.Set("Content-Type", "application/json") router.ServeHTTP(wMsg, reqMsg) require.Equal(t, http.StatusOK, wMsg.Code) // Toggle typing without conversation ID (uses latest conversation) w := httptest.NewRecorder() body, _ := json.Marshal(map[string]interface{}{"typing_status": "on"}) req, _ := http.NewRequest("POST", "/api/v1/widget/conversations/toggle_typing?cw_conversation="+authToken, bytes.NewReader(body)) req.Header.Set("Content-Type", "application/json") router.ServeHTTP(w, req) assert.Equal(t, http.StatusOK, w.Code) } // ---------- UpdateLastSeen ---------- func TestWidgetHandler_Cov3_UpdateLastSeen_NoToken_ChatwootRoute_Cov3(t *testing.T) { _, router, _ := setupWidgetHandlerTest(t) w := httptest.NewRecorder() req, _ := http.NewRequest("POST", "/api/v1/widget/conversations/update_last_seen", nil) router.ServeHTTP(w, req) assert.Equal(t, http.StatusNotFound, w.Code) } func TestWidgetHandler_Cov3_UpdateLastSeen_Success_Cov3(t *testing.T) { db, router, _ := setupWidgetHandlerTest(t) seedWidgetHandlerData(t, db) wConfig := httptest.NewRecorder() reqConfig, _ := http.NewRequest("POST", "/api/v1/widget/config?website_token=handler_ws_token_123", nil) router.ServeHTTP(wConfig, reqConfig) require.Equal(t, http.StatusOK, wConfig.Code) var configResp map[string]interface{} require.NoError(t, json.Unmarshal(wConfig.Body.Bytes(), &configResp)) authToken := configResp["contact"].(map[string]interface{})["pubsub_token"].(string) // Send message to create conversation messageBody, _ := json.Marshal(map[string]interface{}{"message": map[string]interface{}{"content": "hello"}}) wMsg := httptest.NewRecorder() reqMsg, _ := http.NewRequest("POST", "/api/v1/widget/messages?cw_conversation="+authToken, bytes.NewReader(messageBody)) reqMsg.Header.Set("Content-Type", "application/json") router.ServeHTTP(wMsg, reqMsg) require.Equal(t, http.StatusOK, wMsg.Code) w := httptest.NewRecorder() req, _ := http.NewRequest("POST", "/api/v1/widget/conversations/update_last_seen?cw_conversation="+authToken, nil) router.ServeHTTP(w, req) assert.Equal(t, http.StatusOK, w.Code) } // ---------- ToggleStatus ---------- func TestWidgetHandler_Cov3_ToggleStatus_NoToken_Cov3(t *testing.T) { _, router, _ := setupWidgetHandlerTest(t) w := httptest.NewRecorder() req, _ := http.NewRequest("GET", "/api/v1/widget/conversations/toggle_status", nil) router.ServeHTTP(w, req) assert.Equal(t, http.StatusUnauthorized, w.Code) } func TestWidgetHandler_Cov3_ToggleStatus_Success_Cov3(t *testing.T) { db, router, _ := setupWidgetHandlerTest(t) seedWidgetHandlerData(t, db) wConfig := httptest.NewRecorder() reqConfig, _ := http.NewRequest("POST", "/api/v1/widget/config?website_token=handler_ws_token_123", nil) router.ServeHTTP(wConfig, reqConfig) require.Equal(t, http.StatusOK, wConfig.Code) var configResp map[string]interface{} require.NoError(t, json.Unmarshal(wConfig.Body.Bytes(), &configResp)) authToken := configResp["contact"].(map[string]interface{})["pubsub_token"].(string) // Send message to create conversation messageBody, _ := json.Marshal(map[string]interface{}{"message": map[string]interface{}{"content": "hello"}}) wMsg := httptest.NewRecorder() reqMsg, _ := http.NewRequest("POST", "/api/v1/widget/messages?cw_conversation="+authToken, bytes.NewReader(messageBody)) reqMsg.Header.Set("Content-Type", "application/json") router.ServeHTTP(wMsg, reqMsg) require.Equal(t, http.StatusOK, wMsg.Code) w := httptest.NewRecorder() req, _ := http.NewRequest("GET", "/api/v1/widget/conversations/toggle_status?cw_conversation="+authToken, nil) router.ServeHTTP(w, req) assert.Equal(t, http.StatusOK, w.Code) } // ---------- SetConversationCustomAttributes ---------- func TestWidgetHandler_Cov3_SetConvAttrs_NoToken_ChatwootRoute_Cov3(t *testing.T) { _, router, _ := setupWidgetHandlerTest(t) body, _ := json.Marshal(map[string]interface{}{"custom_attributes": map[string]any{"key": "val"}}) w := httptest.NewRecorder() req, _ := http.NewRequest("POST", "/api/v1/widget/conversations/set_custom_attributes", bytes.NewReader(body)) req.Header.Set("Content-Type", "application/json") router.ServeHTTP(w, req) assert.Equal(t, http.StatusNotFound, w.Code) } func TestWidgetHandler_Cov3_SetConvAttrs_InvalidBody_Cov3(t *testing.T) { _, router, _ := setupWidgetHandlerTest(t) w := httptest.NewRecorder() req, _ := http.NewRequest("POST", "/api/v1/widget/conversations/set_custom_attributes?cw_conversation=some-token", bytes.NewReader([]byte("not json"))) req.Header.Set("Content-Type", "application/json") router.ServeHTTP(w, req) assert.Equal(t, http.StatusBadRequest, w.Code) } // ---------- DestroyConversationCustomAttributes ---------- func TestWidgetHandler_Cov3_DestroyConvAttrs_NoToken_ChatwootRoute_Cov3(t *testing.T) { _, router, _ := setupWidgetHandlerTest(t) body, _ := json.Marshal(map[string]interface{}{"custom_attribute": []string{"key1"}}) w := httptest.NewRecorder() req, _ := http.NewRequest("POST", "/api/v1/widget/conversations/destroy_custom_attributes", bytes.NewReader(body)) req.Header.Set("Content-Type", "application/json") router.ServeHTTP(w, req) assert.Equal(t, http.StatusNotFound, w.Code) } func TestWidgetHandler_Cov3_DestroyConvAttrs_InvalidBody_Cov3(t *testing.T) { _, router, _ := setupWidgetHandlerTest(t) w := httptest.NewRecorder() req, _ := http.NewRequest("POST", "/api/v1/widget/conversations/destroy_custom_attributes?cw_conversation=some-token", bytes.NewReader([]byte("not json"))) req.Header.Set("Content-Type", "application/json") router.ServeHTTP(w, req) assert.Equal(t, http.StatusBadRequest, w.Code) } // ---------- ListInboxMembers ---------- func TestWidgetHandler_Cov3_ListInboxMembers_NoToken_ChatwootRoute_Cov3(t *testing.T) { _, router, _ := setupWidgetHandlerTest(t) w := httptest.NewRecorder() req, _ := http.NewRequest("GET", "/api/v1/widget/inbox_members", nil) router.ServeHTTP(w, req) assert.Equal(t, http.StatusNotFound, w.Code) } func TestWidgetHandler_Cov3_ListInboxMembers_Success_Cov3(t *testing.T) { db, router, _ := setupWidgetHandlerTest(t) seedWidgetHandlerData(t, db) w := httptest.NewRecorder() req, _ := http.NewRequest("GET", "/api/v1/widget/inbox_members?website_token=handler_ws_token_123", nil) router.ServeHTTP(w, req) require.Equal(t, http.StatusOK, w.Code, w.Body.String()) } // ---------- ListCampaigns ---------- func TestWidgetHandler_Cov3_ListCampaigns_NoToken_ChatwootRoute_Cov3(t *testing.T) { _, router, _ := setupWidgetHandlerTest(t) w := httptest.NewRecorder() req, _ := http.NewRequest("GET", "/api/v1/widget/campaigns", nil) router.ServeHTTP(w, req) assert.Equal(t, http.StatusNotFound, w.Code) } func TestWidgetHandler_Cov3_ListCampaigns_Success_Cov3(t *testing.T) { db, router, _ := setupWidgetHandlerTest(t) seedWidgetHandlerData(t, db) w := httptest.NewRecorder() req, _ := http.NewRequest("GET", "/api/v1/widget/campaigns?website_token=handler_ws_token_123", nil) router.ServeHTTP(w, req) require.Equal(t, http.StatusOK, w.Code, w.Body.String()) } // ---------- CreateEvent ---------- func TestWidgetHandler_Cov3_CreateEvent_InvalidBody_Cov3(t *testing.T) { _, router, _ := setupWidgetHandlerTest(t) w := httptest.NewRecorder() req, _ := http.NewRequest("POST", "/api/v1/widget/events", bytes.NewReader([]byte("not json"))) req.Header.Set("Content-Type", "application/json") router.ServeHTTP(w, req) assert.Equal(t, http.StatusBadRequest, w.Code) } func TestWidgetHandler_Cov3_CreateEvent_Success_Cov3(t *testing.T) { db, router, _ := setupWidgetHandlerTest(t) seedWidgetHandlerData(t, db) body, _ := json.Marshal(map[string]interface{}{"name": "page_view", "website_token": "handler_ws_token_123"}) w := httptest.NewRecorder() req, _ := http.NewRequest("POST", "/api/v1/widget/events?website_token=handler_ws_token_123", bytes.NewReader(body)) req.Header.Set("Content-Type", "application/json") router.ServeHTTP(w, req) assert.True(t, w.Code == http.StatusNoContent || w.Code == http.StatusBadRequest, "expected 204 or 400, got %d: %s", w.Code, w.Body.String()) } // ---------- AddLabel ---------- func TestWidgetHandler_Cov3_AddLabel_NoToken_ChatwootRoute_Cov3(t *testing.T) { _, router, _ := setupWidgetHandlerTest(t) body, _ := json.Marshal(map[string]interface{}{"label": "test"}) w := httptest.NewRecorder() req, _ := http.NewRequest("POST", "/api/v1/widget/labels", bytes.NewReader(body)) req.Header.Set("Content-Type", "application/json") router.ServeHTTP(w, req) assert.Equal(t, http.StatusNotFound, w.Code) } func TestWidgetHandler_Cov3_AddLabel_InvalidBody_Cov3(t *testing.T) { _, router, _ := setupWidgetHandlerTest(t) w := httptest.NewRecorder() req, _ := http.NewRequest("POST", "/api/v1/widget/labels?cw_conversation=some-token", bytes.NewReader([]byte("not json"))) req.Header.Set("Content-Type", "application/json") router.ServeHTTP(w, req) assert.Equal(t, http.StatusBadRequest, w.Code) } // ---------- RemoveLabel ---------- func TestWidgetHandler_Cov3_RemoveLabel_NoToken_ChatwootRoute_Cov3(t *testing.T) { _, router, _ := setupWidgetHandlerTest(t) w := httptest.NewRecorder() req, _ := http.NewRequest("DELETE", "/api/v1/widget/labels/1", nil) router.ServeHTTP(w, req) assert.Equal(t, http.StatusNotFound, w.Code) } // ---------- SendTranscript ---------- func TestWidgetHandler_Cov3_SendTranscript_NoToken_Cov3(t *testing.T) { _, router, _ := setupWidgetHandlerTest(t) w := httptest.NewRecorder() req, _ := http.NewRequest("POST", "/api/v1/widget/conversations/transcript", nil) router.ServeHTTP(w, req) assert.Equal(t, http.StatusUnauthorized, w.Code) } // ---------- AddDyteParticipantToMeeting ---------- func TestWidgetHandler_Cov3_AddDyte_InvalidBody_Cov3(t *testing.T) { _, router, _ := setupWidgetHandlerTest(t) w := httptest.NewRecorder() req, _ := http.NewRequest("POST", "/api/v1/widget/integrations/dyte/add_participant_to_meeting", bytes.NewReader([]byte("not json"))) req.Header.Set("Content-Type", "application/json") router.ServeHTTP(w, req) assert.Equal(t, http.StatusBadRequest, w.Code) } // ---------- SubmitOfflineMessage ---------- func TestWidgetHandler_Cov3_SubmitOfflineMessage_NoWebsiteToken_Cov3(t *testing.T) { _, router, _ := setupWidgetHandlerTest(t) body, _ := json.Marshal(map[string]interface{}{"message": "hello"}) w := httptest.NewRecorder() req, _ := http.NewRequest("POST", "/widget/offline_message", bytes.NewReader(body)) req.Header.Set("Content-Type", "application/json") router.ServeHTTP(w, req) assert.Equal(t, http.StatusBadRequest, w.Code) } func TestWidgetHandler_Cov3_SubmitOfflineMessage_InvalidWebsiteToken_Cov3(t *testing.T) { _, router, _ := setupWidgetHandlerTest(t) body, _ := json.Marshal(map[string]interface{}{"message": "hello"}) w := httptest.NewRecorder() req, _ := http.NewRequest("POST", "/widget/offline_message?website_token=invalid", bytes.NewReader(body)) req.Header.Set("Content-Type", "application/json") router.ServeHTTP(w, req) assert.Equal(t, http.StatusNotFound, w.Code) } // ---------- Public API endpoints ---------- func TestWidgetHandler_Cov3_PublicInboxShow_NotFound_Cov3(t *testing.T) { _, router, _ := setupWidgetHandlerTest(t) w := httptest.NewRecorder() req, _ := http.NewRequest("GET", "/public/api/v1/inboxes/nonexistent", nil) router.ServeHTTP(w, req) assert.True(t, w.Code == http.StatusNotFound || w.Code == http.StatusBadRequest, "expected 404 or 400, got %d: %s", w.Code, w.Body.String()) } func TestWidgetHandler_Cov3_PublicInboxShow_Success_Cov3(t *testing.T) { db, router, _ := setupWidgetHandlerTest(t) _, _, channelAPI := seedPublicAPIInbox(t, db) w := httptest.NewRecorder() req, _ := http.NewRequest("GET", "/public/api/v1/inboxes/"+channelAPI.Identifier, nil) router.ServeHTTP(w, req) require.Equal(t, http.StatusOK, w.Code, w.Body.String()) } func TestWidgetHandler_Cov3_PublicCreateContact_Success_Cov3(t *testing.T) { db, router, _ := setupWidgetHandlerTest(t) _, _, channelAPI := seedPublicAPIInbox(t, db) body, _ := json.Marshal(map[string]interface{}{"name": "New Contact"}) w := httptest.NewRecorder() req, _ := http.NewRequest("POST", "/public/api/v1/inboxes/"+channelAPI.Identifier+"/contacts", bytes.NewReader(body)) req.Header.Set("Content-Type", "application/json") router.ServeHTTP(w, req) require.Equal(t, http.StatusOK, w.Code, w.Body.String()) var resp map[string]interface{} require.NoError(t, json.Unmarshal(w.Body.Bytes(), &resp)) assert.NotNil(t, resp["source_id"]) assert.NotNil(t, resp["id"]) assert.Equal(t, "New Contact", resp["name"]) } func TestWidgetHandler_PublicContactIgnoresCID(t *testing.T) { db, router, _ := setupWidgetHandlerTest(t) _, _, channelAPI := seedPublicAPIInbox(t, db) body, _ := json.Marshal(map[string]any{"name": "Visitor", "source_id": "visitor", "channel_metadata": map[string]any{"cid": "forged"}}) w := httptest.NewRecorder() req, _ := http.NewRequest(http.MethodPost, "/public/api/v1/inboxes/"+channelAPI.Identifier+"/contacts", bytes.NewReader(body)) req.Header.Set("Content-Type", "application/json") router.ServeHTTP(w, req) require.Equal(t, http.StatusOK, w.Code, w.Body.String()) var contactInbox model.ContactInbox require.NoError(t, db.Where("source_id = ?", "visitor").First(&contactInbox).Error) assert.NotContains(t, string(contactInbox.ChannelMetadata), "forged") } func TestWidgetHandler_Cov3_PublicUpdateContact_Success_Cov3(t *testing.T) { db, router, _ := setupWidgetHandlerTest(t) _, _, channelAPI := seedPublicAPIInbox(t, db) // Create contact first createBody, _ := json.Marshal(map[string]interface{}{"name": "Original"}) wCreate := httptest.NewRecorder() reqCreate, _ := http.NewRequest("POST", "/public/api/v1/inboxes/"+channelAPI.Identifier+"/contacts", bytes.NewReader(createBody)) reqCreate.Header.Set("Content-Type", "application/json") router.ServeHTTP(wCreate, reqCreate) require.Equal(t, http.StatusOK, wCreate.Code) var createResp map[string]interface{} require.NoError(t, json.Unmarshal(wCreate.Body.Bytes(), &createResp)) sourceID := createResp["source_id"].(string) // Update updateBody, _ := json.Marshal(map[string]interface{}{"name": "Updated Name"}) w := httptest.NewRecorder() req, _ := http.NewRequest("PUT", "/public/api/v1/inboxes/"+channelAPI.Identifier+"/contacts/"+sourceID, bytes.NewReader(updateBody)) req.Header.Set("Content-Type", "application/json") router.ServeHTTP(w, req) require.Equal(t, http.StatusOK, w.Code, w.Body.String()) var resp map[string]interface{} require.NoError(t, json.Unmarshal(w.Body.Bytes(), &resp)) assert.Equal(t, "Updated Name", resp["name"]) } func TestWidgetHandler_Cov3_PublicListConversations_Success_Cov3(t *testing.T) { db, router, _ := setupWidgetHandlerTest(t) _, _, channelAPI := seedPublicAPIInbox(t, db) // Create contact createBody, _ := json.Marshal(map[string]interface{}{"name": "Test"}) wCreate := httptest.NewRecorder() reqCreate, _ := http.NewRequest("POST", "/public/api/v1/inboxes/"+channelAPI.Identifier+"/contacts", bytes.NewReader(createBody)) reqCreate.Header.Set("Content-Type", "application/json") router.ServeHTTP(wCreate, reqCreate) require.Equal(t, http.StatusOK, wCreate.Code) var createResp map[string]interface{} require.NoError(t, json.Unmarshal(wCreate.Body.Bytes(), &createResp)) sourceID := createResp["source_id"].(string) // List conversations (should be empty) w := httptest.NewRecorder() req, _ := http.NewRequest("GET", "/public/api/v1/inboxes/"+channelAPI.Identifier+"/contacts/"+sourceID+"/conversations", nil) router.ServeHTTP(w, req) require.Equal(t, http.StatusOK, w.Code, w.Body.String()) } func TestWidgetHandler_Cov3_PublicCreateConversation_Success_Cov3(t *testing.T) { db, router, _ := setupWidgetHandlerTest(t) _, _, channelAPI := seedPublicAPIInbox(t, db) // Create contact createBody, _ := json.Marshal(map[string]interface{}{"name": "Test"}) wCreate := httptest.NewRecorder() reqCreate, _ := http.NewRequest("POST", "/public/api/v1/inboxes/"+channelAPI.Identifier+"/contacts", bytes.NewReader(createBody)) reqCreate.Header.Set("Content-Type", "application/json") router.ServeHTTP(wCreate, reqCreate) require.Equal(t, http.StatusOK, wCreate.Code) var createResp map[string]interface{} require.NoError(t, json.Unmarshal(wCreate.Body.Bytes(), &createResp)) sourceID := createResp["source_id"].(string) // Create conversation convBody, _ := json.Marshal(map[string]interface{}{"custom_attributes": map[string]any{"topic": "support"}}) w := httptest.NewRecorder() req, _ := http.NewRequest("POST", "/public/api/v1/inboxes/"+channelAPI.Identifier+"/contacts/"+sourceID+"/conversations", bytes.NewReader(convBody)) req.Header.Set("Content-Type", "application/json") router.ServeHTTP(w, req) require.Equal(t, http.StatusOK, w.Code, w.Body.String()) var resp map[string]interface{} require.NoError(t, json.Unmarshal(w.Body.Bytes(), &resp)) assert.NotNil(t, resp["id"]) } func TestWidgetHandler_Cov3_PublicToggleTyping_InvalidStatus_Cov3(t *testing.T) { db, router, _ := setupWidgetHandlerTest(t) _, _, channelAPI := seedPublicAPIInbox(t, db) // Create contact createBody, _ := json.Marshal(map[string]interface{}{"name": "Test"}) wCreate := httptest.NewRecorder() reqCreate, _ := http.NewRequest("POST", "/public/api/v1/inboxes/"+channelAPI.Identifier+"/contacts", bytes.NewReader(createBody)) reqCreate.Header.Set("Content-Type", "application/json") router.ServeHTTP(wCreate, reqCreate) require.Equal(t, http.StatusOK, wCreate.Code) var createResp map[string]interface{} require.NoError(t, json.Unmarshal(wCreate.Body.Bytes(), &createResp)) sourceID := createResp["source_id"].(string) // Create conversation convBody, _ := json.Marshal(map[string]interface{}{}) wConv := httptest.NewRecorder() reqConv, _ := http.NewRequest("POST", "/public/api/v1/inboxes/"+channelAPI.Identifier+"/contacts/"+sourceID+"/conversations", bytes.NewReader(convBody)) reqConv.Header.Set("Content-Type", "application/json") router.ServeHTTP(wConv, reqConv) require.Equal(t, http.StatusOK, wConv.Code) var convResp map[string]interface{} require.NoError(t, json.Unmarshal(wConv.Body.Bytes(), &convResp)) convID := convResp["id"].(float64) // Toggle typing with invalid status → returns 200 immediately body, _ := json.Marshal(map[string]interface{}{"typing_status": "invalid"}) w := httptest.NewRecorder() req, _ := http.NewRequest("POST", "/public/api/v1/inboxes/"+channelAPI.Identifier+"/contacts/"+sourceID+"/conversations/"+fmt.Sprint(uint(convID))+"/toggle_typing", bytes.NewReader(body)) req.Header.Set("Content-Type", "application/json") router.ServeHTTP(w, req) assert.Equal(t, http.StatusOK, w.Code) } func TestWidgetHandler_Cov3_PublicToggleStatus_Success_Cov3(t *testing.T) { db, router, _ := setupWidgetHandlerTest(t) _, _, channelAPI := seedPublicAPIInbox(t, db) // Create contact + conversation createBody, _ := json.Marshal(map[string]interface{}{"name": "Test"}) wCreate := httptest.NewRecorder() reqCreate, _ := http.NewRequest("POST", "/public/api/v1/inboxes/"+channelAPI.Identifier+"/contacts", bytes.NewReader(createBody)) reqCreate.Header.Set("Content-Type", "application/json") router.ServeHTTP(wCreate, reqCreate) require.Equal(t, http.StatusOK, wCreate.Code) var createResp map[string]interface{} require.NoError(t, json.Unmarshal(wCreate.Body.Bytes(), &createResp)) sourceID := createResp["source_id"].(string) convBody, _ := json.Marshal(map[string]interface{}{}) wConv := httptest.NewRecorder() reqConv, _ := http.NewRequest("POST", "/public/api/v1/inboxes/"+channelAPI.Identifier+"/contacts/"+sourceID+"/conversations", bytes.NewReader(convBody)) reqConv.Header.Set("Content-Type", "application/json") router.ServeHTTP(wConv, reqConv) require.Equal(t, http.StatusOK, wConv.Code) var convResp map[string]interface{} require.NoError(t, json.Unmarshal(wConv.Body.Bytes(), &convResp)) convID := convResp["id"].(float64) // Toggle status w := httptest.NewRecorder() req, _ := http.NewRequest("POST", "/public/api/v1/inboxes/"+channelAPI.Identifier+"/contacts/"+sourceID+"/conversations/"+fmt.Sprint(uint(convID))+"/toggle_status", nil) router.ServeHTTP(w, req) require.Equal(t, http.StatusOK, w.Code, w.Body.String()) } func TestWidgetHandler_Cov3_PublicUpdateLastSeen_Success_Cov3(t *testing.T) { db, router, _ := setupWidgetHandlerTest(t) _, _, channelAPI := seedPublicAPIInbox(t, db) // Create contact + conversation createBody, _ := json.Marshal(map[string]interface{}{"name": "Test"}) wCreate := httptest.NewRecorder() reqCreate, _ := http.NewRequest("POST", "/public/api/v1/inboxes/"+channelAPI.Identifier+"/contacts", bytes.NewReader(createBody)) reqCreate.Header.Set("Content-Type", "application/json") router.ServeHTTP(wCreate, reqCreate) require.Equal(t, http.StatusOK, wCreate.Code) var createResp map[string]interface{} require.NoError(t, json.Unmarshal(wCreate.Body.Bytes(), &createResp)) sourceID := createResp["source_id"].(string) convBody, _ := json.Marshal(map[string]interface{}{}) wConv := httptest.NewRecorder() reqConv, _ := http.NewRequest("POST", "/public/api/v1/inboxes/"+channelAPI.Identifier+"/contacts/"+sourceID+"/conversations", bytes.NewReader(convBody)) reqConv.Header.Set("Content-Type", "application/json") router.ServeHTTP(wConv, reqConv) require.Equal(t, http.StatusOK, wConv.Code) var convResp map[string]interface{} require.NoError(t, json.Unmarshal(wConv.Body.Bytes(), &convResp)) convID := convResp["id"].(float64) // Update last seen w := httptest.NewRecorder() req, _ := http.NewRequest("POST", "/public/api/v1/inboxes/"+channelAPI.Identifier+"/contacts/"+sourceID+"/conversations/"+fmt.Sprint(uint(convID))+"/update_last_seen", nil) router.ServeHTTP(w, req) assert.Equal(t, http.StatusOK, w.Code) } func TestWidgetHandler_Cov3_PublicListMessages_Success_Cov3(t *testing.T) { db, router, _ := setupWidgetHandlerTest(t) _, _, channelAPI := seedPublicAPIInbox(t, db) // Create contact + conversation createBody, _ := json.Marshal(map[string]interface{}{"name": "Test"}) wCreate := httptest.NewRecorder() reqCreate, _ := http.NewRequest("POST", "/public/api/v1/inboxes/"+channelAPI.Identifier+"/contacts", bytes.NewReader(createBody)) reqCreate.Header.Set("Content-Type", "application/json") router.ServeHTTP(wCreate, reqCreate) require.Equal(t, http.StatusOK, wCreate.Code) var createResp map[string]interface{} require.NoError(t, json.Unmarshal(wCreate.Body.Bytes(), &createResp)) sourceID := createResp["source_id"].(string) convBody, _ := json.Marshal(map[string]interface{}{}) wConv := httptest.NewRecorder() reqConv, _ := http.NewRequest("POST", "/public/api/v1/inboxes/"+channelAPI.Identifier+"/contacts/"+sourceID+"/conversations", bytes.NewReader(convBody)) reqConv.Header.Set("Content-Type", "application/json") router.ServeHTTP(wConv, reqConv) require.Equal(t, http.StatusOK, wConv.Code) var convResp map[string]interface{} require.NoError(t, json.Unmarshal(wConv.Body.Bytes(), &convResp)) convID := convResp["id"].(float64) // List messages w := httptest.NewRecorder() req, _ := http.NewRequest("GET", "/public/api/v1/inboxes/"+channelAPI.Identifier+"/contacts/"+sourceID+"/conversations/"+fmt.Sprint(uint(convID))+"/messages", nil) router.ServeHTTP(w, req) require.Equal(t, http.StatusOK, w.Code, w.Body.String()) } func TestWidgetHandler_Cov3_PublicCreateMessage_Success_Cov3(t *testing.T) { db, router, _ := setupWidgetHandlerTest(t) _, _, channelAPI := seedPublicAPIInbox(t, db) // Create contact + conversation createBody, _ := json.Marshal(map[string]interface{}{"name": "Test"}) wCreate := httptest.NewRecorder() reqCreate, _ := http.NewRequest("POST", "/public/api/v1/inboxes/"+channelAPI.Identifier+"/contacts", bytes.NewReader(createBody)) reqCreate.Header.Set("Content-Type", "application/json") router.ServeHTTP(wCreate, reqCreate) require.Equal(t, http.StatusOK, wCreate.Code) var createResp map[string]interface{} require.NoError(t, json.Unmarshal(wCreate.Body.Bytes(), &createResp)) sourceID := createResp["source_id"].(string) convBody, _ := json.Marshal(map[string]interface{}{}) wConv := httptest.NewRecorder() reqConv, _ := http.NewRequest("POST", "/public/api/v1/inboxes/"+channelAPI.Identifier+"/contacts/"+sourceID+"/conversations", bytes.NewReader(convBody)) reqConv.Header.Set("Content-Type", "application/json") router.ServeHTTP(wConv, reqConv) require.Equal(t, http.StatusOK, wConv.Code) var convResp map[string]interface{} require.NoError(t, json.Unmarshal(wConv.Body.Bytes(), &convResp)) convID := convResp["id"].(float64) // Create message msgBody, _ := json.Marshal(map[string]interface{}{"content": "Hello from public API"}) w := httptest.NewRecorder() req, _ := http.NewRequest("POST", "/public/api/v1/inboxes/"+channelAPI.Identifier+"/contacts/"+sourceID+"/conversations/"+fmt.Sprint(uint(convID))+"/messages", bytes.NewReader(msgBody)) req.Header.Set("Content-Type", "application/json") router.ServeHTTP(w, req) require.Equal(t, http.StatusOK, w.Code, w.Body.String()) var resp map[string]interface{} require.NoError(t, json.Unmarshal(w.Body.Bytes(), &resp)) assert.Equal(t, "Hello from public API", resp["content"]) } func TestWidgetHandler_Cov3_PublicGetConversation_Success_Cov3(t *testing.T) { db, router, _ := setupWidgetHandlerTest(t) _, _, channelAPI := seedPublicAPIInbox(t, db) // Create contact + conversation createBody, _ := json.Marshal(map[string]interface{}{"name": "Test"}) wCreate := httptest.NewRecorder() reqCreate, _ := http.NewRequest("POST", "/public/api/v1/inboxes/"+channelAPI.Identifier+"/contacts", bytes.NewReader(createBody)) reqCreate.Header.Set("Content-Type", "application/json") router.ServeHTTP(wCreate, reqCreate) require.Equal(t, http.StatusOK, wCreate.Code) var createResp map[string]interface{} require.NoError(t, json.Unmarshal(wCreate.Body.Bytes(), &createResp)) sourceID := createResp["source_id"].(string) convBody, _ := json.Marshal(map[string]interface{}{}) wConv := httptest.NewRecorder() reqConv, _ := http.NewRequest("POST", "/public/api/v1/inboxes/"+channelAPI.Identifier+"/contacts/"+sourceID+"/conversations", bytes.NewReader(convBody)) reqConv.Header.Set("Content-Type", "application/json") router.ServeHTTP(wConv, reqConv) require.Equal(t, http.StatusOK, wConv.Code) var convResp map[string]interface{} require.NoError(t, json.Unmarshal(wConv.Body.Bytes(), &convResp)) convID := convResp["id"].(float64) // Get conversation w := httptest.NewRecorder() req, _ := http.NewRequest("GET", "/public/api/v1/inboxes/"+channelAPI.Identifier+"/contacts/"+sourceID+"/conversations/"+fmt.Sprint(uint(convID)), nil) router.ServeHTTP(w, req) require.Equal(t, http.StatusOK, w.Code, w.Body.String()) var resp map[string]interface{} require.NoError(t, json.Unmarshal(w.Body.Bytes(), &resp)) assert.NotNil(t, resp["id"]) } func TestWidgetHandler_Cov3_PublicUintParam_Invalid_Cov3(t *testing.T) { db, router, _ := setupWidgetHandlerTest(t) _, _, channelAPI := seedPublicAPIInbox(t, db) // Create contact createBody, _ := json.Marshal(map[string]interface{}{"name": "Test"}) wCreate := httptest.NewRecorder() reqCreate, _ := http.NewRequest("POST", "/public/api/v1/inboxes/"+channelAPI.Identifier+"/contacts", bytes.NewReader(createBody)) reqCreate.Header.Set("Content-Type", "application/json") router.ServeHTTP(wCreate, reqCreate) require.Equal(t, http.StatusOK, wCreate.Code) var createResp map[string]interface{} require.NoError(t, json.Unmarshal(wCreate.Body.Bytes(), &createResp)) sourceID := createResp["source_id"].(string) // Invalid conversation ID w := httptest.NewRecorder() req, _ := http.NewRequest("GET", "/public/api/v1/inboxes/"+channelAPI.Identifier+"/contacts/"+sourceID+"/conversations/abc", nil) router.ServeHTTP(w, req) assert.Equal(t, http.StatusBadRequest, w.Code) } // Ensure imports are used var _ = gorm.ErrRecordNotFound