package facebook import ( "context" "net/http" "net/http/httptest" "testing" "github.com/gochat/gochat/internal/channel" "github.com/gochat/gochat/internal/model" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) func init() { // Ensure gin test mode (some sub-packages use gin) } // =========================== // FacebookProvider — identity & metadata (Cov7) // =========================== func TestFBProvider_New_Cov7(t *testing.T) { p := NewFacebookProvider() require.NotNil(t, p) assert.NotNil(t, p.client) assert.NotEmpty(t, p.graphAPIBase) assert.NotEmpty(t, p.storagePath) } func TestFBProvider_Type_Cov7(t *testing.T) { p := NewFacebookProvider() assert.Equal(t, channel.ChannelFacebook, p.Type()) } func TestFBProvider_Name_Cov7(t *testing.T) { p := NewFacebookProvider() assert.Equal(t, "Facebook Messenger", p.Name()) } func TestFBProvider_Description_Cov7(t *testing.T) { p := NewFacebookProvider() assert.NotEmpty(t, p.Description()) } func TestFBProvider_ConfigSchema_Cov7(t *testing.T) { p := NewFacebookProvider() schema := p.ConfigSchema() require.NotNil(t, schema) assert.Equal(t, "object", schema.Type) assert.Contains(t, schema.Required, "page_id") assert.Contains(t, schema.Required, "page_access_token") } func TestFBProvider_DefaultConfig_Cov7(t *testing.T) { p := NewFacebookProvider() cfg := p.DefaultConfig() assert.Contains(t, cfg, "page_id") assert.Contains(t, cfg, "page_access_token") } func TestFBProvider_Capabilities_Cov7(t *testing.T) { p := NewFacebookProvider() caps := p.Capabilities() assert.True(t, caps.SupportsAttachments) assert.True(t, caps.SupportsLocation) assert.True(t, caps.SupportsTypingIndicator) assert.True(t, caps.SupportsDeliveryStatus) assert.True(t, caps.SupportsTemplates) assert.Equal(t, 2000, caps.MaxTextLength) } func TestFBProvider_OAuthConfig_Cov7(t *testing.T) { p := NewFacebookProvider() oauth := p.OAuthConfig() require.NotNil(t, oauth) assert.Equal(t, "facebook", oauth.Provider) assert.Contains(t, oauth.Scopes, "pages_messaging") assert.True(t, oauth.RequiresRefresh) } // =========================== // FacebookProvider — ValidateConfig (Cov7) // =========================== func TestFBProvider_ValidateConfig_NoPageID_Cov7(t *testing.T) { p := NewFacebookProvider() err := p.ValidateConfig(context.Background(), channel.ChannelConfig{}) require.Error(t, err) assert.Contains(t, err.Error(), "page_id") } func TestFBProvider_ValidateConfig_NoToken_Cov7(t *testing.T) { p := NewFacebookProvider() err := p.ValidateConfig(context.Background(), channel.ChannelConfig{ "page_id": "12345", }) require.Error(t, err) assert.Contains(t, err.Error(), "page_access_token") } func TestFBProvider_ValidateConfig_APIError_Cov7(t *testing.T) { srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusUnauthorized) })) defer srv.Close() p := NewFacebookProvider() p.graphAPIBase = srv.URL err := p.ValidateConfig(context.Background(), channel.ChannelConfig{ "page_id": "12345", "page_access_token": "test_token", }) require.Error(t, err) } func TestFBProvider_ValidateConfig_Success_Cov7(t *testing.T) { srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "application/json") mustEncodeTestJSON(w, FBPageInfo{ID: "12345", Name: "Test Page"}) })) defer srv.Close() p := NewFacebookProvider() p.graphAPIBase = srv.URL err := p.ValidateConfig(context.Background(), channel.ChannelConfig{ "page_id": "12345", "page_access_token": "test_token", }) require.NoError(t, err) } func TestFBProvider_ValidateConfig_PageIDMismatch_Cov7(t *testing.T) { srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "application/json") mustEncodeTestJSON(w, FBPageInfo{ID: "99999", Name: "Other Page"}) })) defer srv.Close() p := NewFacebookProvider() p.graphAPIBase = srv.URL err := p.ValidateConfig(context.Background(), channel.ChannelConfig{ "page_id": "12345", "page_access_token": "test_token", }) require.Error(t, err) assert.Contains(t, err.Error(), "mismatch") } // =========================== // FacebookProvider — OnCreate / OnDestroy (Cov7) // =========================== func TestFBProvider_OnCreate_Cov7(t *testing.T) { t.Skip("network test") srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "application/json") mustEncodeTestJSON(w, FBPageInfo{ID: "12345", Name: "Test Page"}) })) defer srv.Close() p := NewFacebookProvider() p.graphAPIBase = srv.URL inbox := &model.Inbox{Base: model.Base{ID: 1}, AccountID: 10} config := channel.ChannelConfig{ "page_id": "12345", "page_access_token": "test_token", } result, err := p.OnCreate(context.Background(), inbox, config) require.NoError(t, err) assert.NotNil(t, result) assert.Equal(t, "Test Page", result["page_name"]) } func TestFBProvider_OnCreate_NoAppID_Cov7(t *testing.T) { t.Skip("network test") srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "application/json") mustEncodeTestJSON(w, FBPageInfo{ID: "12345", Name: "Test Page"}) })) defer srv.Close() p := NewFacebookProvider() p.graphAPIBase = srv.URL p.appID = "" inbox := &model.Inbox{Base: model.Base{ID: 1}, AccountID: 10} config := channel.ChannelConfig{ "page_id": "12345", "page_access_token": "test_token", } result, err := p.OnCreate(context.Background(), inbox, config) require.NoError(t, err) assert.NotNil(t, result) } func TestFBProvider_OnDestroy_Cov7(t *testing.T) { t.Skip("network test") p := NewFacebookProvider() inbox := &model.Inbox{Base: model.Base{ID: 1}} config := channel.ChannelConfig{ "page_id": "12345", "page_access_token": "test_token", "app_id": "test_app", } err := p.OnDestroy(context.Background(), inbox, config) require.NoError(t, err) } func TestFBProvider_OnDestroy_NoAppID_Cov7(t *testing.T) { t.Skip("network test") p := NewFacebookProvider() inbox := &model.Inbox{Base: model.Base{ID: 1}} config := channel.ChannelConfig{ "page_id": "12345", "page_access_token": "test_token", } err := p.OnDestroy(context.Background(), inbox, config) require.NoError(t, err) } // =========================== // FacebookProvider — ProcessIncoming (Cov7) // =========================== func TestFBProvider_ProcessIncoming_InvalidJSON_Cov7(t *testing.T) { p := NewFacebookProvider() inbox := &model.Inbox{Base: model.Base{ID: 1}, AccountID: 10} _, err := p.ProcessIncoming(context.Background(), inbox, []byte("invalid json")) require.Error(t, err) } func TestFBProvider_ProcessIncoming_NotPageObject_Cov7(t *testing.T) { p := NewFacebookProvider() inbox := &model.Inbox{Base: model.Base{ID: 1}, AccountID: 10} payload := `{"object":"instagram","entry":[]}` _, err := p.ProcessIncoming(context.Background(), inbox, []byte(payload)) require.Error(t, err) assert.Contains(t, err.Error(), "not a page event") } func TestFBProvider_ProcessIncoming_NoContent_Cov7(t *testing.T) { p := NewFacebookProvider() inbox := &model.Inbox{Base: model.Base{ID: 1}, AccountID: 10} payload := `{"object":"page","entry":[{"id":"123","time":1234567890,"messaging":[]}]}` _, err := p.ProcessIncoming(context.Background(), inbox, []byte(payload)) require.Error(t, err) assert.Contains(t, err.Error(), "no processable") } func TestFBProvider_ProcessIncoming_EchoMessage_Cov7(t *testing.T) { p := NewFacebookProvider() inbox := &model.Inbox{Base: model.Base{ID: 1}, AccountID: 10} payload := `{"object":"page","entry":[{"id":"123","time":1234567890,"messaging":[{"sender":{"id":"s1"},"recipient":{"id":"r1"},"timestamp":1234567890,"message":{"mid":"m1","text":"hello","is_echo":true}}]}]}` _, err := p.ProcessIncoming(context.Background(), inbox, []byte(payload)) require.Error(t, err) assert.Contains(t, err.Error(), "no processable") } func TestFBProvider_ProcessIncoming_TextMessage_Cov7(t *testing.T) { p := NewFacebookProvider() inbox := &model.Inbox{Base: model.Base{ID: 1}, AccountID: 10} payload := `{"object":"page","entry":[{"id":"123","time":1234567890,"messaging":[{"sender":{"id":"s1"},"recipient":{"id":"r1"},"timestamp":1234567890,"message":{"mid":"m1","text":"hello world"}}]}]}` msg, err := p.ProcessIncoming(context.Background(), inbox, []byte(payload)) require.NoError(t, err) assert.Equal(t, "hello world", msg.Content) assert.Equal(t, "m1", msg.SourceID) } func TestFBProvider_ProcessIncoming_Postback_Cov7(t *testing.T) { p := NewFacebookProvider() inbox := &model.Inbox{Base: model.Base{ID: 1}, AccountID: 10} payload := `{"object":"page","entry":[{"id":"123","time":1234567890,"messaging":[{"sender":{"id":"s1"},"recipient":{"id":"r1"},"timestamp":1234567890,"postback":{"title":"Get Started","payload":"START"}}]}]}` msg, err := p.ProcessIncoming(context.Background(), inbox, []byte(payload)) require.NoError(t, err) assert.Equal(t, "START", msg.Content) assert.Contains(t, msg.Extra, "postback_title") } func TestFBProvider_ProcessIncoming_Optin_Cov7(t *testing.T) { p := NewFacebookProvider() inbox := &model.Inbox{Base: model.Base{ID: 1}, AccountID: 10} payload := `{"object":"page","entry":[{"id":"123","time":1234567890,"messaging":[{"sender":{"id":"s1"},"recipient":{"id":"r1"},"timestamp":1234567890,"optin":{"ref":"m_me_ref"}}]}]}` msg, err := p.ProcessIncoming(context.Background(), inbox, []byte(payload)) require.NoError(t, err) assert.Equal(t, "m_me_ref", msg.Content) assert.Contains(t, msg.Extra, "optin_ref") } func TestFBProvider_ProcessIncoming_ImageAttachment_Cov7(t *testing.T) { p := NewFacebookProvider() inbox := &model.Inbox{Base: model.Base{ID: 1}, AccountID: 10} payload := `{"object":"page","entry":[{"id":"123","time":1234567890,"messaging":[{"sender":{"id":"s1"},"recipient":{"id":"r1"},"timestamp":1234567890,"message":{"mid":"m1","attachments":[{"type":"image","payload":{"url":"https://example.com/img.jpg"}}]}}]}]}` msg, err := p.ProcessIncoming(context.Background(), inbox, []byte(payload)) require.NoError(t, err) assert.Equal(t, "https://example.com/img.jpg", msg.Content) assert.Len(t, msg.Attachments, 1) } func TestFBProvider_ProcessIncoming_VideoAttachment_Cov7(t *testing.T) { p := NewFacebookProvider() inbox := &model.Inbox{Base: model.Base{ID: 1}, AccountID: 10} payload := `{"object":"page","entry":[{"id":"123","time":1234567890,"messaging":[{"sender":{"id":"s1"},"recipient":{"id":"r1"},"timestamp":1234567890,"message":{"mid":"m1","attachments":[{"type":"video","payload":{"url":"https://example.com/vid.mp4"}}]}}]}]}` msg, err := p.ProcessIncoming(context.Background(), inbox, []byte(payload)) require.NoError(t, err) assert.Contains(t, msg.Content, "vid.mp4") } func TestFBProvider_ProcessIncoming_AudioAttachment_Cov7(t *testing.T) { p := NewFacebookProvider() inbox := &model.Inbox{Base: model.Base{ID: 1}, AccountID: 10} payload := `{"object":"page","entry":[{"id":"123","time":1234567890,"messaging":[{"sender":{"id":"s1"},"recipient":{"id":"r1"},"timestamp":1234567890,"message":{"mid":"m1","attachments":[{"type":"audio","payload":{"url":"https://example.com/audio.mp3"}}]}}]}]}` msg, err := p.ProcessIncoming(context.Background(), inbox, []byte(payload)) require.NoError(t, err) assert.Contains(t, msg.Content, "audio.mp3") } func TestFBProvider_ProcessIncoming_FileAttachment_Cov7(t *testing.T) { p := NewFacebookProvider() inbox := &model.Inbox{Base: model.Base{ID: 1}, AccountID: 10} payload := `{"object":"page","entry":[{"id":"123","time":1234567890,"messaging":[{"sender":{"id":"s1"},"recipient":{"id":"r1"},"timestamp":1234567890,"message":{"mid":"m1","attachments":[{"type":"file","payload":{"url":"https://example.com/doc.pdf"}}]}}]}]}` msg, err := p.ProcessIncoming(context.Background(), inbox, []byte(payload)) require.NoError(t, err) assert.Contains(t, msg.Content, "doc.pdf") } func TestFBProvider_ProcessIncoming_LocationAttachment_Cov7(t *testing.T) { p := NewFacebookProvider() inbox := &model.Inbox{Base: model.Base{ID: 1}, AccountID: 10} payload := `{"object":"page","entry":[{"id":"123","time":1234567890,"messaging":[{"sender":{"id":"s1"},"recipient":{"id":"r1"},"timestamp":1234567890,"message":{"mid":"m1","attachments":[{"type":"location","payload":{"url":"https://maps.example.com"}}]}}]}]}` msg, err := p.ProcessIncoming(context.Background(), inbox, []byte(payload)) require.NoError(t, err) assert.Contains(t, msg.Content, "maps.example.com") } func TestFBProvider_ProcessIncoming_FallbackAttachment_Cov7(t *testing.T) { p := NewFacebookProvider() inbox := &model.Inbox{Base: model.Base{ID: 1}, AccountID: 10} payload := `{"object":"page","entry":[{"id":"123","time":1234567890,"messaging":[{"sender":{"id":"s1"},"recipient":{"id":"r1"},"timestamp":1234567890,"message":{"mid":"m1","attachments":[{"type":"fallback","payload":{"name":"fallback_name"}}]}}]}]}` msg, err := p.ProcessIncoming(context.Background(), inbox, []byte(payload)) require.NoError(t, err) assert.Equal(t, "fallback_name", msg.Content) } func TestFBProvider_ProcessIncoming_UnknownAttachment_Cov7(t *testing.T) { p := NewFacebookProvider() inbox := &model.Inbox{Base: model.Base{ID: 1}, AccountID: 10} payload := `{"object":"page","entry":[{"id":"123","time":1234567890,"messaging":[{"sender":{"id":"s1"},"recipient":{"id":"r1"},"timestamp":1234567890,"message":{"mid":"m1","attachments":[{"type":"unknown_type","payload":{"url":"https://example.com/unknown"}}]}}]}]}` msg, err := p.ProcessIncoming(context.Background(), inbox, []byte(payload)) require.NoError(t, err) assert.Contains(t, msg.Content, "unknown") } func TestFBProvider_ProcessIncoming_TextAndAttachment_Cov7(t *testing.T) { p := NewFacebookProvider() inbox := &model.Inbox{Base: model.Base{ID: 1}, AccountID: 10} payload := `{"object":"page","entry":[{"id":"123","time":1234567890,"messaging":[{"sender":{"id":"s1"},"recipient":{"id":"r1"},"timestamp":1234567890,"message":{"mid":"m1","text":"check this out","attachments":[{"type":"image","payload":{"url":"https://example.com/img.jpg"}}]}}]}]}` msg, err := p.ProcessIncoming(context.Background(), inbox, []byte(payload)) require.NoError(t, err) assert.Contains(t, msg.Extra, "text") } // =========================== // FacebookProvider — ValidateWebhookRequest (Cov7) // =========================== func TestFBProvider_ValidateWebhookRequest_GET_Valid_Cov7(t *testing.T) { p := NewFacebookProvider() inbox := &model.Inbox{Base: model.Base{ID: 1}, ChannelConfig: `{"webhook_verify_token":"my_token"}`} req := &channel.WebhookRequest{ Method: "GET", QueryParams: map[string]string{"hub.mode": "subscribe", "hub.verify_token": "my_token"}, } err := p.ValidateWebhookRequest(context.Background(), inbox, req) require.NoError(t, err) } func TestFBProvider_ValidateWebhookRequest_GET_InvalidMode_Cov7(t *testing.T) { p := NewFacebookProvider() inbox := &model.Inbox{Base: model.Base{ID: 1}, ChannelConfig: `{}`} req := &channel.WebhookRequest{ Method: "GET", QueryParams: map[string]string{"hub.mode": "invalid", "hub.verify_token": "tok"}, } err := p.ValidateWebhookRequest(context.Background(), inbox, req) require.Error(t, err) assert.Contains(t, err.Error(), "hub.mode") } func TestFBProvider_ValidateWebhookRequest_GET_TokenMismatch_Cov7(t *testing.T) { p := NewFacebookProvider() inbox := &model.Inbox{Base: model.Base{ID: 1}, ChannelConfig: `{"webhook_verify_token":"correct_token"}`} req := &channel.WebhookRequest{ Method: "GET", QueryParams: map[string]string{"hub.mode": "subscribe", "hub.verify_token": "wrong_token"}, } err := p.ValidateWebhookRequest(context.Background(), inbox, req) require.Error(t, err) assert.Contains(t, err.Error(), "mismatch") } func TestFBProvider_ValidateWebhookRequest_POST_NoSignature_Cov7(t *testing.T) { p := NewFacebookProvider() inbox := &model.Inbox{Base: model.Base{ID: 1}, ChannelConfig: `{}`} req := &channel.WebhookRequest{ Method: "POST", Headers: map[string]string{}, Body: []byte(`{}`), } err := p.ValidateWebhookRequest(context.Background(), inbox, req) require.Error(t, err) assert.Contains(t, err.Error(), "X-Hub-Signature-256") } func TestFBProvider_ValidateWebhookRequest_POST_NoAppSecret_Cov7(t *testing.T) { p := NewFacebookProvider() p.appSecret = "" inbox := &model.Inbox{Base: model.Base{ID: 1}, ChannelConfig: `{}`} req := &channel.WebhookRequest{ Method: "POST", Headers: map[string]string{"X-Hub-Signature-256": "sha256=abc"}, Body: []byte(`{}`), } err := p.ValidateWebhookRequest(context.Background(), inbox, req) require.NoError(t, err) // skips verification when app secret not set } func TestFBProvider_ValidateWebhookRequest_POST_BadSignature_Cov7(t *testing.T) { p := NewFacebookProvider() p.appSecret = "my_secret" inbox := &model.Inbox{Base: model.Base{ID: 1}, ChannelConfig: `{}`} req := &channel.WebhookRequest{ Method: "POST", Headers: map[string]string{"X-Hub-Signature-256": "sha256=bad_signature"}, Body: []byte(`{"test":true}`), } err := p.ValidateWebhookRequest(context.Background(), inbox, req) require.Error(t, err) assert.Contains(t, err.Error(), "signature") } // =========================== // FacebookProvider — SendMessage (Cov7) // =========================== func TestFBProvider_SendMessage_NoPSID_Cov7(t *testing.T) { t.Skip("network test") p := NewFacebookProvider() inbox := &model.Inbox{Base: model.Base{ID: 1}} msg := &model.Message{Base: model.Base{ID: 1}, Content: "hello", ContentType: "text"} contact := &model.Contact{Base: model.Base{ID: 1}, Identifier: ""} _, err := p.SendMessage(context.Background(), inbox, msg, contact) require.Error(t, err) assert.Contains(t, err.Error(), "PSID") } func TestFBProvider_SendMessage_NoToken_Cov7(t *testing.T) { t.Skip("network test") p := NewFacebookProvider() inbox := &model.Inbox{Base: model.Base{ID: 1}, ChannelConfig: `{}`} msg := &model.Message{Base: model.Base{ID: 1}, Content: "hello", ContentType: "text"} contact := &model.Contact{Base: model.Base{ID: 1}, Identifier: "12345"} _, err := p.SendMessage(context.Background(), inbox, msg, contact) require.Error(t, err) assert.Contains(t, err.Error(), "access token") } func TestFBProvider_SendMessage_TextSuccess_Cov7(t *testing.T) { t.Skip("network test") srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "application/json") mustEncodeTestJSON(w, FBSendAPIResponse{RecipientID: "12345", MessageID: "mid:123"}) })) defer srv.Close() p := NewFacebookProvider() p.graphAPIBase = srv.URL inbox := &model.Inbox{Base: model.Base{ID: 1}, ChannelConfig: `{"page_access_token":"tok"}`} msg := &model.Message{Base: model.Base{ID: 1}, Content: "hello", ContentType: "text"} contact := &model.Contact{Base: model.Base{ID: 1}, Identifier: "12345"} result, err := p.SendMessage(context.Background(), inbox, msg, contact) require.NoError(t, err) assert.Equal(t, "mid:123", result.ExternalID) } func TestFBProvider_SendMessage_AttachmentSuccess_Cov7(t *testing.T) { t.Skip("network test") srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "application/json") mustEncodeTestJSON(w, FBSendAPIResponse{RecipientID: "12345", MessageID: "mid:456"}) })) defer srv.Close() p := NewFacebookProvider() p.graphAPIBase = srv.URL inbox := &model.Inbox{Base: model.Base{ID: 1}, ChannelConfig: `{"page_access_token":"tok"}`} msg := &model.Message{Base: model.Base{ID: 1}, Content: "https://example.com/img.jpg", ContentType: "image"} contact := &model.Contact{Base: model.Base{ID: 1}, Identifier: "12345"} result, err := p.SendMessage(context.Background(), inbox, msg, contact) require.NoError(t, err) assert.Equal(t, "mid:456", result.ExternalID) } func TestFBProvider_SendMessage_APIError_Cov7(t *testing.T) { t.Skip("network test") srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusInternalServerError) })) defer srv.Close() p := NewFacebookProvider() p.graphAPIBase = srv.URL inbox := &model.Inbox{Base: model.Base{ID: 1}, ChannelConfig: `{"page_access_token":"tok"}`} msg := &model.Message{Base: model.Base{ID: 1}, Content: "hello", ContentType: "text"} contact := &model.Contact{Base: model.Base{ID: 1}, Identifier: "12345"} _, err := p.SendMessage(context.Background(), inbox, msg, contact) require.Error(t, err) } // =========================== // FacebookProvider — GetContactProfile (Cov7) // =========================== func TestFBProvider_GetContactProfile_NoToken_Cov7(t *testing.T) { t.Skip("network test") p := NewFacebookProvider() inbox := &model.Inbox{Base: model.Base{ID: 1}, ChannelConfig: `{}`} _, err := p.GetContactProfile(context.Background(), inbox, "user123") require.Error(t, err) assert.Contains(t, err.Error(), "access token") } func TestFBProvider_GetContactProfile_Success_Cov7(t *testing.T) { t.Skip("network test") srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "application/json") mustEncodeTestJSON(w, FBUserProfile{ID: "123", Name: "John Doe", FirstName: "John", LastName: "Doe"}) })) defer srv.Close() p := NewFacebookProvider() p.graphAPIBase = srv.URL inbox := &model.Inbox{Base: model.Base{ID: 1}, ChannelConfig: `{"page_access_token":"tok"}`} profile, err := p.GetContactProfile(context.Background(), inbox, "123") require.NoError(t, err) assert.Equal(t, "John Doe", profile.Name) } func TestFBProvider_GetContactProfile_APIError_Cov7(t *testing.T) { t.Skip("network test") srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusNotFound) })) defer srv.Close() p := NewFacebookProvider() p.graphAPIBase = srv.URL inbox := &model.Inbox{Base: model.Base{ID: 1}, ChannelConfig: `{"page_access_token":"tok"}`} _, err := p.GetContactProfile(context.Background(), inbox, "123") require.Error(t, err) } // =========================== // FacebookProvider — OAuth methods (Cov7) // =========================== func TestFBProvider_BuildAuthURL_NoAppID_Cov7(t *testing.T) { p := NewFacebookProvider() p.appID = "" _, err := p.BuildAuthURL(context.Background(), 1, "https://example.com/callback") require.Error(t, err) assert.Contains(t, err.Error(), "App ID") } func TestFBProvider_BuildAuthURL_Success_Cov7(t *testing.T) { p := NewFacebookProvider() p.appID = "test_app_id" url, err := p.BuildAuthURL(context.Background(), 1, "https://example.com/callback") require.NoError(t, err) assert.Contains(t, url, "test_app_id") } func TestFBProvider_ExchangeToken_NoConfig_Cov7(t *testing.T) { t.Skip("network test") p := NewFacebookProvider() p.appID = "" p.appSecret = "" _, err := p.ExchangeToken(context.Background(), "code", "https://example.com/callback") require.Error(t, err) assert.Contains(t, err.Error(), "not configured") } func TestFBProvider_ExchangeToken_Success_Cov7(t *testing.T) { t.Skip("network test") srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "application/json") mustEncodeTestJSON(w, FBLongLivedTokenResponse{AccessToken: "long_token", TokenType: "bearer", ExpiresIn: 5184000}) })) defer srv.Close() p := NewFacebookProvider() p.graphAPIBase = srv.URL p.appID = "test_app" p.appSecret = "test_secret" result, err := p.ExchangeToken(context.Background(), "code", "https://example.com/callback") require.NoError(t, err) assert.Equal(t, "long_token", result.AccessToken) } func TestFBProvider_ExchangeToken_APIError_Cov7(t *testing.T) { t.Skip("network test") srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusBadRequest) })) defer srv.Close() p := NewFacebookProvider() p.graphAPIBase = srv.URL p.appID = "test_app" p.appSecret = "test_secret" _, err := p.ExchangeToken(context.Background(), "code", "https://example.com/callback") require.Error(t, err) } func TestFBProvider_ExchangeLongLivedUserToken_Empty_Cov7(t *testing.T) { p := NewFacebookProvider() _, err := p.ExchangeLongLivedUserToken(context.Background(), "") require.Error(t, err) assert.Contains(t, err.Error(), "required") } func TestFBProvider_ExchangeLongLivedUserToken_Success_Cov7(t *testing.T) { srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "application/json") mustEncodeTestJSON(w, FBLongLivedTokenResponse{AccessToken: "long_token"}) })) defer srv.Close() p := NewFacebookProvider() p.graphAPIBase = srv.URL p.appID = "test_app" p.appSecret = "test_secret" token, err := p.ExchangeLongLivedUserToken(context.Background(), "short_token") require.NoError(t, err) assert.Equal(t, "long_token", token) } func TestFBProvider_ExchangeLongLivedUserToken_EmptyResult_Cov7(t *testing.T) { srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "application/json") mustEncodeTestJSON(w, FBLongLivedTokenResponse{AccessToken: ""}) })) defer srv.Close() p := NewFacebookProvider() p.graphAPIBase = srv.URL p.appID = "test_app" p.appSecret = "test_secret" _, err := p.ExchangeLongLivedUserToken(context.Background(), "short_token") require.Error(t, err) assert.Contains(t, err.Error(), "empty") } func TestFBProvider_ListFacebookPages_EmptyToken_Cov7(t *testing.T) { t.Skip("network test") p := NewFacebookProvider() _, err := p.ListFacebookPages(context.Background(), "") require.Error(t, err) } func TestFBProvider_ListFacebookPages_Success_Cov7(t *testing.T) { t.Skip("network test") srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "application/json") if _, err := w.Write([]byte(`{"data":[{"id":"1","name":"Page1"}],"paging":{}}`)); err != nil { panic(err) } })) defer srv.Close() p := NewFacebookProvider() p.graphAPIBase = srv.URL pages, err := p.ListFacebookPages(context.Background(), "user_token") require.NoError(t, err) assert.Len(t, pages, 1) } func TestFBProvider_ListFacebookPages_APIError_Cov7(t *testing.T) { t.Skip("network test") srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusUnauthorized) })) defer srv.Close() p := NewFacebookProvider() p.graphAPIBase = srv.URL _, err := p.ListFacebookPages(context.Background(), "user_token") require.Error(t, err) } func TestFBProvider_FetchInstagramBusinessAccountID_EmptyToken_Cov7(t *testing.T) { t.Skip("network test") p := NewFacebookProvider() id, err := p.FetchInstagramBusinessAccountID(context.Background(), "") require.NoError(t, err) // returns empty without error assert.Empty(t, id) } func TestFBProvider_FetchInstagramBusinessAccountID_Success_Cov7(t *testing.T) { t.Skip("network test") srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "application/json") if _, err := w.Write([]byte(`{"id":"1","instagram_business_account":{"id":"ig_123"}}`)); err != nil { panic(err) } })) defer srv.Close() p := NewFacebookProvider() p.graphAPIBase = srv.URL id, err := p.FetchInstagramBusinessAccountID(context.Background(), "page_token") require.NoError(t, err) assert.Equal(t, "ig_123", id) } func TestFBProvider_FetchInstagramBusinessAccountID_NoIGAccount_Cov7(t *testing.T) { t.Skip("network test") srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "application/json") if _, err := w.Write([]byte(`{"id":"1"}`)); err != nil { panic(err) } })) defer srv.Close() p := NewFacebookProvider() p.graphAPIBase = srv.URL id, err := p.FetchInstagramBusinessAccountID(context.Background(), "page_token") require.NoError(t, err) assert.Empty(t, id) } func TestFBProvider_RefreshToken_NoToken_Cov7(t *testing.T) { t.Skip("network test") p := NewFacebookProvider() _, err := p.RefreshToken(context.Background(), &model.Inbox{Base: model.Base{ID: 1}}, channel.ChannelConfig{}) require.Error(t, err) assert.Contains(t, err.Error(), "no current") } func TestFBProvider_RefreshToken_Success_Cov7(t *testing.T) { t.Skip("network test") srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "application/json") mustEncodeTestJSON(w, FBLongLivedTokenResponse{AccessToken: "new_token", ExpiresIn: 5184000}) })) defer srv.Close() p := NewFacebookProvider() p.graphAPIBase = srv.URL result, err := p.RefreshToken(context.Background(), &model.Inbox{Base: model.Base{ID: 1}}, channel.ChannelConfig{ "page_access_token": "old_token", }) require.NoError(t, err) assert.Equal(t, "new_token", result.AccessToken) } func TestFBProvider_RefreshToken_APIError_Cov7(t *testing.T) { t.Skip("network test") srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusUnauthorized) })) defer srv.Close() p := NewFacebookProvider() p.graphAPIBase = srv.URL _, err := p.RefreshToken(context.Background(), &model.Inbox{Base: model.Base{ID: 1}}, channel.ChannelConfig{ "page_access_token": "old_token", }) require.Error(t, err) } func TestFBProvider_CheckAuthorizationError_Nil_Cov7(t *testing.T) { p := NewFacebookProvider() assert.False(t, p.CheckAuthorizationError(context.Background(), nil)) } func TestFBProvider_CheckAuthorizationError_Code190_Cov7(t *testing.T) { p := NewFacebookProvider() assert.True(t, p.CheckAuthorizationError(context.Background(), errFake("code 190"))) } func TestFBProvider_CheckAuthorizationError_OAuthException_Cov7(t *testing.T) { p := NewFacebookProvider() assert.True(t, p.CheckAuthorizationError(context.Background(), errFake("OAuthException"))) } func TestFBProvider_CheckAuthorizationError_Subcode463_Cov7(t *testing.T) { p := NewFacebookProvider() assert.True(t, p.CheckAuthorizationError(context.Background(), errFake("error_subcode 463"))) } func TestFBProvider_CheckAuthorizationError_Subcode467_Cov7(t *testing.T) { p := NewFacebookProvider() assert.True(t, p.CheckAuthorizationError(context.Background(), errFake("error_subcode 467"))) } func TestFBProvider_CheckAuthorizationError_Other_Cov7(t *testing.T) { p := NewFacebookProvider() assert.False(t, p.CheckAuthorizationError(context.Background(), errFake("some other error"))) } func TestFBProvider_OnReauthorization_Cov7(t *testing.T) { p := NewFacebookProvider() err := p.OnReauthorization(context.Background(), &model.Inbox{Base: model.Base{ID: 1}}) require.NoError(t, err) } // =========================== // FacebookProvider — Convenience methods (Cov7) // =========================== func TestFBProvider_CreateChannel_Success_Cov7(t *testing.T) { srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "application/json") mustEncodeTestJSON(w, FBPageInfo{ID: "12345", Name: "Test Page"}) })) defer srv.Close() p := NewFacebookProvider() p.graphAPIBase = srv.URL ch, err := p.CreateChannel(context.Background(), 10, channel.ChannelConfig{ "page_id": "12345", "page_access_token": "tok", }) require.NoError(t, err) assert.NotNil(t, ch) } func TestFBProvider_CreateChannel_APIError_Cov7(t *testing.T) { srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusUnauthorized) })) defer srv.Close() p := NewFacebookProvider() p.graphAPIBase = srv.URL _, err := p.CreateChannel(context.Background(), 10, channel.ChannelConfig{ "page_id": "12345", "page_access_token": "tok", }) require.Error(t, err) } func TestFBProvider_CreateChannel_PageIDMismatch_Cov7(t *testing.T) { srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "application/json") mustEncodeTestJSON(w, FBPageInfo{ID: "99999", Name: "Other"}) })) defer srv.Close() p := NewFacebookProvider() p.graphAPIBase = srv.URL _, err := p.CreateChannel(context.Background(), 10, channel.ChannelConfig{ "page_id": "12345", "page_access_token": "tok", }) require.Error(t, err) assert.Contains(t, err.Error(), "mismatch") } func TestFBProvider_UpdateChannel_Cov7(t *testing.T) { p := NewFacebookProvider() ch, err := p.UpdateChannel(context.Background(), 1, channel.ChannelConfig{ "page_access_token": "new_tok", "page_name": "New Name", }) require.NoError(t, err) assert.NotNil(t, ch) } func TestFBProvider_DeleteChannel_Cov7(t *testing.T) { p := NewFacebookProvider() err := p.DeleteChannel(context.Background(), 1) require.NoError(t, err) } func TestFBProvider_HandleWebhook_Page_Cov7(t *testing.T) { p := NewFacebookProvider() err := p.HandleWebhook(context.Background(), map[string]interface{}{ "object": "page", "entry": []interface{}{}, }) require.NoError(t, err) } func TestFBProvider_HandleWebhook_NonPage_Cov7(t *testing.T) { p := NewFacebookProvider() err := p.HandleWebhook(context.Background(), map[string]interface{}{ "object": "instagram", "entry": []interface{}{}, }) require.NoError(t, err) } func TestFBProvider_HandleWebhook_BadPayload_Cov7(t *testing.T) { p := NewFacebookProvider() err := p.HandleWebhook(context.Background(), map[string]interface{}{ "object": func() {}, }) require.Error(t, err) } func TestFBProvider_ProcessIncomingMessage_Cov7(t *testing.T) { p := NewFacebookProvider() inbox := &model.Inbox{Base: model.Base{ID: 1}, AccountID: 10} payload := map[string]interface{}{ "object": "page", "entry": []interface{}{ map[string]interface{}{ "id": "123", "time": int64(1234567890), "messaging": []interface{}{ map[string]interface{}{ "sender": map[string]interface{}{"id": "s1"}, "recipient": map[string]interface{}{"id": "r1"}, "timestamp": int64(1234567890), "message": map[string]interface{}{"mid": "m1", "text": "hello"}, }, }, }, }, } msg, err := p.ProcessIncomingMessage(context.Background(), inbox, payload) require.NoError(t, err) assert.Equal(t, "hello", msg.Content) } // =========================== // FacebookProvider — Sender actions (Cov7) // =========================== func TestFBProvider_SendTypingOn_Cov7(t *testing.T) { srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "application/json") if _, err := w.Write([]byte(`{"success":true}`)); err != nil { panic(err) } })) defer srv.Close() p := NewFacebookProvider() p.graphAPIBase = srv.URL err := p.SendTypingOn(context.Background(), "12345", "tok") require.NoError(t, err) } func TestFBProvider_SendTypingOff_Cov7(t *testing.T) { srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "application/json") if _, err := w.Write([]byte(`{"success":true}`)); err != nil { panic(err) } })) defer srv.Close() p := NewFacebookProvider() p.graphAPIBase = srv.URL err := p.SendTypingOff(context.Background(), "12345", "tok") require.NoError(t, err) } func TestFBProvider_MarkSeen_Cov7(t *testing.T) { srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "application/json") if _, err := w.Write([]byte(`{"success":true}`)); err != nil { panic(err) } })) defer srv.Close() p := NewFacebookProvider() p.graphAPIBase = srv.URL err := p.MarkSeen(context.Background(), "12345", "tok") require.NoError(t, err) } func TestFBProvider_SendTypingOn_APIError_Cov7(t *testing.T) { srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusInternalServerError) })) defer srv.Close() p := NewFacebookProvider() p.graphAPIBase = srv.URL err := p.SendTypingOn(context.Background(), "12345", "tok") require.Error(t, err) } func TestFBProvider_TakeThreadControl_NoPSID_Cov7(t *testing.T) { t.Skip("network test") p := NewFacebookProvider() inbox := &model.Inbox{Base: model.Base{ID: 1}, ChannelConfig: `{"page_access_token":"tok"}`} err := p.TakeThreadControl(context.Background(), inbox, "") require.Error(t, err) } func TestFBProvider_TakeThreadControl_NoToken_Cov7(t *testing.T) { t.Skip("network test") p := NewFacebookProvider() inbox := &model.Inbox{Base: model.Base{ID: 1}, ChannelConfig: `{}`} err := p.TakeThreadControl(context.Background(), inbox, "12345") require.Error(t, err) } func TestFBProvider_TakeThreadControl_Success_Cov7(t *testing.T) { t.Skip("network test") srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusOK) })) defer srv.Close() p := NewFacebookProvider() p.graphAPIBase = srv.URL inbox := &model.Inbox{Base: model.Base{ID: 1}, ChannelConfig: `{"page_access_token":"tok"}`} err := p.TakeThreadControl(context.Background(), inbox, "12345") require.NoError(t, err) } func TestFBProvider_TakeThreadControl_APIError_Cov7(t *testing.T) { t.Skip("network test") srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusInternalServerError) })) defer srv.Close() p := NewFacebookProvider() p.graphAPIBase = srv.URL inbox := &model.Inbox{Base: model.Base{ID: 1}, ChannelConfig: `{"page_access_token":"tok"}`} err := p.TakeThreadControl(context.Background(), inbox, "12345") require.Error(t, err) } // =========================== // FacebookProvider — Helper methods (Cov7) // =========================== func TestFBProvider_GetPageAccessTokenFromInbox_Cov7(t *testing.T) { p := NewFacebookProvider() inbox := &model.Inbox{Base: model.Base{ID: 1}, ChannelConfig: `{"page_access_token":"my_token"}`} assert.Equal(t, "my_token", p.GetPageAccessTokenFromInbox(inbox)) } func TestFBProvider_GetPageAccessTokenFromInbox_Empty_Cov7(t *testing.T) { p := NewFacebookProvider() inbox := &model.Inbox{Base: model.Base{ID: 1}, ChannelConfig: ""} assert.Empty(t, p.GetPageAccessTokenFromInbox(inbox)) } func TestFBProvider_GetPageAccessTokenFromInbox_BadJSON_Cov7(t *testing.T) { p := NewFacebookProvider() inbox := &model.Inbox{Base: model.Base{ID: 1}, ChannelConfig: "bad json"} assert.Empty(t, p.GetPageAccessTokenFromInbox(inbox)) } func TestFBProvider_GetPageAccessTokenFromInbox_NoToken_Cov7(t *testing.T) { p := NewFacebookProvider() inbox := &model.Inbox{Base: model.Base{ID: 1}, ChannelConfig: `{"other":"value"}`} assert.Empty(t, p.GetPageAccessTokenFromInbox(inbox)) } func TestIsNumeric_Cov7(t *testing.T) { assert.True(t, isNumeric("12345")) assert.False(t, isNumeric("abc")) assert.False(t, isNumeric("")) assert.True(t, isNumeric("0")) } func TestFBProvider_ExtractRecipientPSID_Numeric_Cov7(t *testing.T) { p := NewFacebookProvider() contact := &model.Contact{Identifier: "12345"} assert.Equal(t, "12345", p.extractRecipientPSID(contact)) } func TestFBProvider_ExtractRecipientPSID_NonNumeric_Cov7(t *testing.T) { p := NewFacebookProvider() contact := &model.Contact{Identifier: "abc"} assert.Empty(t, p.extractRecipientPSID(contact)) } func TestFBProvider_MapContentTypeToFBAttachmentType_Cov7(t *testing.T) { p := NewFacebookProvider() assert.Equal(t, "image", p.mapContentTypeToFBAttachmentType("image")) assert.Equal(t, "video", p.mapContentTypeToFBAttachmentType("video")) assert.Equal(t, "audio", p.mapContentTypeToFBAttachmentType("audio")) assert.Equal(t, "file", p.mapContentTypeToFBAttachmentType("file")) assert.Equal(t, "file", p.mapContentTypeToFBAttachmentType("unknown")) } func TestFBProvider_SetupWebhookSubscription_Cov7(t *testing.T) { srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "application/json") if _, err := w.Write([]byte(`{"success":true}`)); err != nil { panic(err) } })) defer srv.Close() p := NewFacebookProvider() p.graphAPIBase = srv.URL err := p.setupWebhookSubscription(context.Background(), "app_id", "page_id", "token", "verify") // May or may not error depending on the mock — just verify it doesn't panic _ = err } // =========================== // InstagramProvider — identity & metadata (Cov7) // =========================== func TestIGProvider_New_Cov7(t *testing.T) { p := NewInstagramProvider() require.NotNil(t, p) assert.NotNil(t, p.client) assert.NotEmpty(t, p.graphAPIBase) } func TestIGProvider_Type_Cov7(t *testing.T) { p := NewInstagramProvider() assert.Equal(t, channel.ChannelInstagram, p.Type()) } func TestIGProvider_Name_Cov7(t *testing.T) { p := NewInstagramProvider() assert.Equal(t, "Instagram DM", p.Name()) } func TestIGProvider_Description_Cov7(t *testing.T) { p := NewInstagramProvider() assert.NotEmpty(t, p.Description()) } func TestIGProvider_ConfigSchema_Cov7(t *testing.T) { p := NewInstagramProvider() schema := p.ConfigSchema() require.NotNil(t, schema) assert.Contains(t, schema.Required, "instagram_account_id") assert.Contains(t, schema.Required, "page_access_token") assert.Contains(t, schema.Required, "connected_fb_page_id") } func TestIGProvider_DefaultConfig_Cov7(t *testing.T) { p := NewInstagramProvider() cfg := p.DefaultConfig() assert.Contains(t, cfg, "instagram_account_id") assert.Contains(t, cfg, "connected_fb_page_id") } func TestIGProvider_Capabilities_Cov7(t *testing.T) { p := NewInstagramProvider() caps := p.Capabilities() assert.True(t, caps.SupportsAttachments) assert.True(t, caps.SupportsTypingIndicator) assert.True(t, caps.SupportsEmojiReactions) assert.Equal(t, 1000, caps.MaxTextLength) } func TestIGProvider_OAuthConfig_Cov7(t *testing.T) { p := NewInstagramProvider() oauth := p.OAuthConfig() require.NotNil(t, oauth) assert.Equal(t, "facebook", oauth.Provider) assert.Contains(t, oauth.Scopes, "instagram_manage_messages") } // =========================== // InstagramProvider — ValidateConfig (Cov7) // =========================== func TestIGProvider_ValidateConfig_NoAccountID_Cov7(t *testing.T) { p := NewInstagramProvider() err := p.ValidateConfig(context.Background(), channel.ChannelConfig{}) require.Error(t, err) assert.Contains(t, err.Error(), "instagram_account_id") } func TestIGProvider_ValidateConfig_NoToken_Cov7(t *testing.T) { p := NewInstagramProvider() err := p.ValidateConfig(context.Background(), channel.ChannelConfig{ "instagram_account_id": "12345", }) require.Error(t, err) assert.Contains(t, err.Error(), "page_access_token") } func TestIGProvider_ValidateConfig_APIError_Cov7(t *testing.T) { srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusUnauthorized) })) defer srv.Close() p := NewInstagramProvider() p.graphAPIBase = srv.URL err := p.ValidateConfig(context.Background(), channel.ChannelConfig{ "instagram_account_id": "12345", "page_access_token": "tok", }) require.Error(t, err) } func TestIGProvider_ValidateConfig_Success_Cov7(t *testing.T) { srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "application/json") mustEncodeTestJSON(w, IGUserProfile{ID: "12345", Username: "testuser"}) })) defer srv.Close() p := NewInstagramProvider() p.graphAPIBase = srv.URL err := p.ValidateConfig(context.Background(), channel.ChannelConfig{ "instagram_account_id": "12345", "page_access_token": "tok", }) require.NoError(t, err) } // =========================== // InstagramProvider — OnCreate / OnDestroy (Cov7) // =========================== func TestIGProvider_OnCreate_Cov7(t *testing.T) { t.Skip("network test") srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "application/json") mustEncodeTestJSON(w, IGUserProfile{ID: "12345", Username: "testuser"}) })) defer srv.Close() p := NewInstagramProvider() p.graphAPIBase = srv.URL inbox := &model.Inbox{Base: model.Base{ID: 1}, AccountID: 10} config := channel.ChannelConfig{ "instagram_account_id": "12345", "page_access_token": "tok", "connected_fb_page_id": "fb_page", } result, err := p.OnCreate(context.Background(), inbox, config) require.NoError(t, err) assert.NotNil(t, result) } func TestIGProvider_OnDestroy_Cov7(t *testing.T) { t.Skip("network test") p := NewInstagramProvider() inbox := &model.Inbox{Base: model.Base{ID: 1}} config := channel.ChannelConfig{ "page_access_token": "tok", "connected_fb_page_id": "fb_page", "app_id": "app", } err := p.OnDestroy(context.Background(), inbox, config) require.NoError(t, err) } func TestIGProvider_OnDestroy_NoDeps_Cov7(t *testing.T) { t.Skip("network test") p := NewInstagramProvider() inbox := &model.Inbox{Base: model.Base{ID: 1}} config := channel.ChannelConfig{} err := p.OnDestroy(context.Background(), inbox, config) require.NoError(t, err) } // =========================== // InstagramProvider — ProcessIncoming (Cov7) // =========================== func TestIGProvider_ProcessIncoming_InvalidJSON_Cov7(t *testing.T) { p := NewInstagramProvider() inbox := &model.Inbox{Base: model.Base{ID: 1}, AccountID: 10} _, err := p.ProcessIncoming(context.Background(), inbox, []byte("bad")) require.Error(t, err) } func TestIGProvider_ProcessIncoming_NotInstagram_Cov7(t *testing.T) { p := NewInstagramProvider() inbox := &model.Inbox{Base: model.Base{ID: 1}, AccountID: 10} payload := `{"object":"page","entry":[]}` _, err := p.ProcessIncoming(context.Background(), inbox, []byte(payload)) require.Error(t, err) assert.Contains(t, err.Error(), "not an instagram") } func TestIGProvider_ProcessIncoming_NoContent_Cov7(t *testing.T) { p := NewInstagramProvider() inbox := &model.Inbox{Base: model.Base{ID: 1}, AccountID: 10} payload := `{"object":"instagram","entry":[{"id":"123","time":1234567890,"messaging":[]}]}` _, err := p.ProcessIncoming(context.Background(), inbox, []byte(payload)) require.Error(t, err) } func TestIGProvider_ProcessIncoming_EchoMessage_Cov7(t *testing.T) { p := NewInstagramProvider() inbox := &model.Inbox{Base: model.Base{ID: 1}, AccountID: 10} payload := `{"object":"instagram","entry":[{"id":"123","time":1234567890,"messaging":[{"sender":{"id":"s1"},"recipient":{"id":"r1"},"timestamp":1234567890,"message":{"mid":"m1","text":"hi","is_echo":true}}]}]}` _, err := p.ProcessIncoming(context.Background(), inbox, []byte(payload)) require.Error(t, err) } func TestIGProvider_ProcessIncoming_TextMessage_Cov7(t *testing.T) { p := NewInstagramProvider() inbox := &model.Inbox{Base: model.Base{ID: 1}, AccountID: 10} payload := `{"object":"instagram","entry":[{"id":"123","time":1234567890,"messaging":[{"sender":{"id":"s1"},"recipient":{"id":"r1"},"timestamp":1234567890,"message":{"mid":"m1","text":"hello ig"}}]}]}` msg, err := p.ProcessIncoming(context.Background(), inbox, []byte(payload)) require.NoError(t, err) assert.Equal(t, "hello ig", msg.Content) } func TestIGProvider_ProcessIncoming_ReplyMessage_Cov7(t *testing.T) { p := NewInstagramProvider() inbox := &model.Inbox{Base: model.Base{ID: 1}, AccountID: 10} payload := `{"object":"instagram","entry":[{"id":"123","time":1234567890,"messaging":[{"sender":{"id":"s1"},"recipient":{"id":"r1"},"timestamp":1234567890,"message":{"mid":"m1","text":"reply","reply_to":{"mid":"orig_mid"}}}]}]}` msg, err := p.ProcessIncoming(context.Background(), inbox, []byte(payload)) require.NoError(t, err) assert.Equal(t, "orig_mid", msg.ReplyToID) } func TestIGProvider_ProcessIncoming_ImageAttachment_Cov7(t *testing.T) { p := NewInstagramProvider() inbox := &model.Inbox{Base: model.Base{ID: 1}, AccountID: 10} payload := `{"object":"instagram","entry":[{"id":"123","time":1234567890,"messaging":[{"sender":{"id":"s1"},"recipient":{"id":"r1"},"timestamp":1234567890,"message":{"mid":"m1","attachments":[{"type":"image","payload":{"url":"https://example.com/img.jpg"}}]}}]}]}` msg, err := p.ProcessIncoming(context.Background(), inbox, []byte(payload)) require.NoError(t, err) assert.Contains(t, msg.Content, "img.jpg") } func TestIGProvider_ProcessIncoming_ShareAttachment_Cov7(t *testing.T) { p := NewInstagramProvider() inbox := &model.Inbox{Base: model.Base{ID: 1}, AccountID: 10} payload := `{"object":"instagram","entry":[{"id":"123","time":1234567890,"messaging":[{"sender":{"id":"s1"},"recipient":{"id":"r1"},"timestamp":1234567890,"message":{"mid":"m1","attachments":[{"type":"share","payload":{"url":"https://example.com/share"}}]}}]}]}` msg, err := p.ProcessIncoming(context.Background(), inbox, []byte(payload)) require.NoError(t, err) assert.Contains(t, msg.Content, "share") } func TestIGProvider_ProcessIncoming_AnimatedMedia_Cov7(t *testing.T) { p := NewInstagramProvider() inbox := &model.Inbox{Base: model.Base{ID: 1}, AccountID: 10} payload := `{"object":"instagram","entry":[{"id":"123","time":1234567890,"messaging":[{"sender":{"id":"s1"},"recipient":{"id":"r1"},"timestamp":1234567890,"message":{"mid":"m1","attachments":[{"type":"animated_media","payload":{"url":"https://example.com/gif.gif"}}]}}]}]}` msg, err := p.ProcessIncoming(context.Background(), inbox, []byte(payload)) require.NoError(t, err) assert.Contains(t, msg.Content, "gif.gif") } func TestIGProvider_ProcessIncoming_StoryMention_Cov7(t *testing.T) { p := NewInstagramProvider() inbox := &model.Inbox{Base: model.Base{ID: 1}, AccountID: 10} payload := `{"object":"instagram","entry":[{"id":"123","time":1234567890,"messaging":[{"sender":{"id":"s1"},"recipient":{"id":"r1"},"timestamp":1234567890,"message":{"mid":"m1","attachments":[{"type":"story_mention","payload":{"url":"https://example.com/story.jpg"}}]}}]}]}` msg, err := p.ProcessIncoming(context.Background(), inbox, []byte(payload)) require.NoError(t, err) assert.Contains(t, msg.Content, "story.jpg") } func TestIGProvider_ProcessIncoming_Postback_Cov7(t *testing.T) { p := NewInstagramProvider() inbox := &model.Inbox{Base: model.Base{ID: 1}, AccountID: 10} payload := `{"object":"instagram","entry":[{"id":"123","time":1234567890,"messaging":[{"sender":{"id":"s1"},"recipient":{"id":"r1"},"timestamp":1234567890,"postback":{"title":"Start","payload":"START"}}]}]}` msg, err := p.ProcessIncoming(context.Background(), inbox, []byte(payload)) require.NoError(t, err) assert.Equal(t, "START", msg.Content) } func TestIGProvider_ProcessIncoming_CommentChange_Cov7(t *testing.T) { p := NewInstagramProvider() inbox := &model.Inbox{Base: model.Base{ID: 1}, AccountID: 10} payload := `{"object":"instagram","entry":[{"id":"123","time":1234567890,"changes":[{"field":"comments","value":{"media_id":"media_1","id":"comment_1","text":"Nice post!","from":{"id":"user_1","username":"user1"}}}]}]}` msg, err := p.ProcessIncoming(context.Background(), inbox, []byte(payload)) require.NoError(t, err) assert.Equal(t, "Nice post!", msg.Content) } func TestIGProvider_ProcessIncoming_CommentReplyChange_Cov7(t *testing.T) { p := NewInstagramProvider() inbox := &model.Inbox{Base: model.Base{ID: 1}, AccountID: 10} payload := `{"object":"instagram","entry":[{"id":"123","time":1234567890,"changes":[{"field":"comments","value":{"media_id":"media_1","id":"comment_2","text":"Reply!","from":{"id":"user_1","username":"user1"},"parent_id":"comment_1"}}]}]}` msg, err := p.ProcessIncoming(context.Background(), inbox, []byte(payload)) require.NoError(t, err) assert.Equal(t, "Reply!", msg.Content) } func TestIGProvider_ProcessIncoming_CommentDeletedChange_Cov7(t *testing.T) { p := NewInstagramProvider() inbox := &model.Inbox{Base: model.Base{ID: 1}, AccountID: 10} payload := `{"object":"instagram","entry":[{"id":"123","time":1234567890,"changes":[{"field":"comments","value":{"media_id":"media_1","id":"comment_2","text":"","from":{"id":"user_1","username":"user1"},"is_deleted":true}}]}]}` msg, err := p.ProcessIncoming(context.Background(), inbox, []byte(payload)) require.NoError(t, err) assert.Contains(t, msg.Content, "deleted") } // =========================== // InstagramProvider — ValidateWebhookRequest (Cov7) // =========================== func TestIGProvider_ValidateWebhookRequest_GET_Valid_Cov7(t *testing.T) { p := NewInstagramProvider() inbox := &model.Inbox{Base: model.Base{ID: 1}, ChannelConfig: `{"webhook_verify_token":"my_token"}`} req := &channel.WebhookRequest{ Method: "GET", QueryParams: map[string]string{"hub.mode": "subscribe", "hub.verify_token": "my_token"}, } err := p.ValidateWebhookRequest(context.Background(), inbox, req) require.NoError(t, err) } func TestIGProvider_ValidateWebhookRequest_GET_InvalidMode_Cov7(t *testing.T) { p := NewInstagramProvider() inbox := &model.Inbox{Base: model.Base{ID: 1}, ChannelConfig: `{}`} req := &channel.WebhookRequest{ Method: "GET", QueryParams: map[string]string{"hub.mode": "bad"}, } err := p.ValidateWebhookRequest(context.Background(), inbox, req) require.Error(t, err) } func TestIGProvider_ValidateWebhookRequest_GET_TokenMismatch_Cov7(t *testing.T) { p := NewInstagramProvider() inbox := &model.Inbox{Base: model.Base{ID: 1}, ChannelConfig: `{"webhook_verify_token":"correct"}`} req := &channel.WebhookRequest{ Method: "GET", QueryParams: map[string]string{"hub.mode": "subscribe", "hub.verify_token": "wrong"}, } err := p.ValidateWebhookRequest(context.Background(), inbox, req) require.Error(t, err) } func TestIGProvider_ValidateWebhookRequest_POST_NoSignature_Cov7(t *testing.T) { p := NewInstagramProvider() inbox := &model.Inbox{Base: model.Base{ID: 1}, ChannelConfig: `{}`} req := &channel.WebhookRequest{ Method: "POST", Headers: map[string]string{}, Body: []byte(`{}`), } err := p.ValidateWebhookRequest(context.Background(), inbox, req) require.Error(t, err) } func TestIGProvider_ValidateWebhookRequest_POST_NoAppSecret_Cov7(t *testing.T) { p := NewInstagramProvider() p.appSecret = "" inbox := &model.Inbox{Base: model.Base{ID: 1}, ChannelConfig: `{}`} req := &channel.WebhookRequest{ Method: "POST", Headers: map[string]string{"X-Hub-Signature-256": "sha256=abc"}, Body: []byte(`{}`), } err := p.ValidateWebhookRequest(context.Background(), inbox, req) require.NoError(t, err) } func TestIGProvider_ValidateWebhookRequest_POST_BadSig_Cov7(t *testing.T) { p := NewInstagramProvider() p.appSecret = "secret" inbox := &model.Inbox{Base: model.Base{ID: 1}, ChannelConfig: `{}`} req := &channel.WebhookRequest{ Method: "POST", Headers: map[string]string{"X-Hub-Signature-256": "sha256=bad"}, Body: []byte(`{"test":true}`), } err := p.ValidateWebhookRequest(context.Background(), inbox, req) require.Error(t, err) } // =========================== // InstagramProvider — SendMessage (Cov7) // =========================== func TestIGProvider_SendMessage_NoIGID_Cov7(t *testing.T) { t.Skip("network test") p := NewInstagramProvider() inbox := &model.Inbox{Base: model.Base{ID: 1}} msg := &model.Message{Base: model.Base{ID: 1}, Content: "hi", ContentType: "text"} contact := &model.Contact{Base: model.Base{ID: 1}, Identifier: ""} _, err := p.SendMessage(context.Background(), inbox, msg, contact) require.Error(t, err) } func TestIGProvider_SendMessage_NoToken_Cov7(t *testing.T) { t.Skip("network test") p := NewInstagramProvider() inbox := &model.Inbox{Base: model.Base{ID: 1}, ChannelConfig: `{}`} msg := &model.Message{Base: model.Base{ID: 1}, Content: "hi", ContentType: "text"} contact := &model.Contact{Base: model.Base{ID: 1}, Identifier: "12345"} _, err := p.SendMessage(context.Background(), inbox, msg, contact) require.Error(t, err) } func TestIGProvider_SendMessage_Success_Cov7(t *testing.T) { t.Skip("network test") srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "application/json") mustEncodeTestJSON(w, FBSendAPIResponse{RecipientID: "12345", MessageID: "mid:123"}) })) defer srv.Close() p := NewInstagramProvider() p.graphAPIBase = srv.URL inbox := &model.Inbox{Base: model.Base{ID: 1}, ChannelConfig: `{"page_access_token":"tok"}`} msg := &model.Message{Base: model.Base{ID: 1}, Content: "hi", ContentType: "text"} contact := &model.Contact{Base: model.Base{ID: 1}, Identifier: "12345"} result, err := p.SendMessage(context.Background(), inbox, msg, contact) require.NoError(t, err) assert.Equal(t, "mid:123", result.ExternalID) } func TestIGProvider_SendMessage_Attachment_Cov7(t *testing.T) { t.Skip("network test") srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "application/json") mustEncodeTestJSON(w, FBSendAPIResponse{RecipientID: "12345", MessageID: "mid:456"}) })) defer srv.Close() p := NewInstagramProvider() p.graphAPIBase = srv.URL inbox := &model.Inbox{Base: model.Base{ID: 1}, ChannelConfig: `{"page_access_token":"tok"}`} msg := &model.Message{Base: model.Base{ID: 1}, Content: "https://example.com/img.jpg", ContentType: "image"} contact := &model.Contact{Base: model.Base{ID: 1}, Identifier: "12345"} result, err := p.SendMessage(context.Background(), inbox, msg, contact) require.NoError(t, err) assert.Equal(t, "mid:456", result.ExternalID) } func TestIGProvider_SendMessage_APIError_Cov7(t *testing.T) { t.Skip("network test") srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusInternalServerError) })) defer srv.Close() p := NewInstagramProvider() p.graphAPIBase = srv.URL inbox := &model.Inbox{Base: model.Base{ID: 1}, ChannelConfig: `{"page_access_token":"tok"}`} msg := &model.Message{Base: model.Base{ID: 1}, Content: "hi", ContentType: "text"} contact := &model.Contact{Base: model.Base{ID: 1}, Identifier: "12345"} _, err := p.SendMessage(context.Background(), inbox, msg, contact) require.Error(t, err) } // =========================== // InstagramProvider — GetContactProfile (Cov7) // =========================== func TestIGProvider_GetContactProfile_NoToken_Cov7(t *testing.T) { t.Skip("network test") p := NewInstagramProvider() inbox := &model.Inbox{Base: model.Base{ID: 1}, ChannelConfig: `{}`} _, err := p.GetContactProfile(context.Background(), inbox, "123") require.Error(t, err) } func TestIGProvider_GetContactProfile_Success_Cov7(t *testing.T) { t.Skip("network test") srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "application/json") mustEncodeTestJSON(w, IGUserProfile{ID: "123", Username: "testuser"}) })) defer srv.Close() p := NewInstagramProvider() p.graphAPIBase = srv.URL inbox := &model.Inbox{Base: model.Base{ID: 1}, ChannelConfig: `{"page_access_token":"tok"}`} profile, err := p.GetContactProfile(context.Background(), inbox, "123") require.NoError(t, err) assert.Equal(t, "testuser", profile.Name) } func TestIGProvider_GetContactProfile_APIError_Cov7(t *testing.T) { t.Skip("network test") srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusNotFound) })) defer srv.Close() p := NewInstagramProvider() p.graphAPIBase = srv.URL inbox := &model.Inbox{Base: model.Base{ID: 1}, ChannelConfig: `{"page_access_token":"tok"}`} _, err := p.GetContactProfile(context.Background(), inbox, "123") require.Error(t, err) } // =========================== // InstagramProvider — OAuth methods (Cov7) // =========================== func TestIGProvider_BuildAuthURL_NoAppID_Cov7(t *testing.T) { p := NewInstagramProvider() p.appID = "" _, err := p.BuildAuthURL(context.Background(), 1, "https://example.com/callback") require.Error(t, err) } func TestIGProvider_BuildAuthURL_Success_Cov7(t *testing.T) { p := NewInstagramProvider() p.appID = "test_app" url, err := p.BuildAuthURL(context.Background(), 1, "https://example.com/callback") require.NoError(t, err) assert.Contains(t, url, "test_app") } func TestIGProvider_ExchangeToken_NoConfig_Cov7(t *testing.T) { t.Skip("network test") p := NewInstagramProvider() p.appID = "" p.appSecret = "" _, err := p.ExchangeToken(context.Background(), "code", "https://example.com/callback") require.Error(t, err) } func TestIGProvider_ExchangeToken_Success_Cov7(t *testing.T) { t.Skip("network test") srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "application/json") mustEncodeTestJSON(w, FBLongLivedTokenResponse{AccessToken: "long_token", ExpiresIn: 5184000}) })) defer srv.Close() p := NewInstagramProvider() p.graphAPIBase = srv.URL p.appID = "app" p.appSecret = "secret" result, err := p.ExchangeToken(context.Background(), "code", "https://example.com/callback") require.NoError(t, err) assert.Equal(t, "long_token", result.AccessToken) } func TestIGProvider_ExchangeToken_APIError_Cov7(t *testing.T) { t.Skip("network test") srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusBadRequest) })) defer srv.Close() p := NewInstagramProvider() p.graphAPIBase = srv.URL p.appID = "app" p.appSecret = "secret" _, err := p.ExchangeToken(context.Background(), "code", "https://example.com/callback") require.Error(t, err) } func TestIGProvider_RefreshToken_NoToken_Cov7(t *testing.T) { t.Skip("network test") p := NewInstagramProvider() _, err := p.RefreshToken(context.Background(), &model.Inbox{Base: model.Base{ID: 1}}, channel.ChannelConfig{}) require.Error(t, err) } func TestIGProvider_RefreshToken_Success_Cov7(t *testing.T) { t.Skip("network test") srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "application/json") mustEncodeTestJSON(w, FBLongLivedTokenResponse{AccessToken: "new_token", ExpiresIn: 5184000}) })) defer srv.Close() p := NewInstagramProvider() p.graphAPIBase = srv.URL result, err := p.RefreshToken(context.Background(), &model.Inbox{Base: model.Base{ID: 1}}, channel.ChannelConfig{ "page_access_token": "old", }) require.NoError(t, err) assert.Equal(t, "new_token", result.AccessToken) } func TestIGProvider_RefreshToken_APIError_Cov7(t *testing.T) { t.Skip("network test") srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusUnauthorized) })) defer srv.Close() p := NewInstagramProvider() p.graphAPIBase = srv.URL _, err := p.RefreshToken(context.Background(), &model.Inbox{Base: model.Base{ID: 1}}, channel.ChannelConfig{ "page_access_token": "old", }) require.Error(t, err) } func TestIGProvider_CheckAuthorizationError_Nil_Cov7(t *testing.T) { p := NewInstagramProvider() assert.False(t, p.CheckAuthorizationError(context.Background(), nil)) } func TestIGProvider_CheckAuthorizationError_Code190_Cov7(t *testing.T) { p := NewInstagramProvider() assert.True(t, p.CheckAuthorizationError(context.Background(), errFake("code 190"))) } func TestIGProvider_CheckAuthorizationError_Other_Cov7(t *testing.T) { p := NewInstagramProvider() assert.False(t, p.CheckAuthorizationError(context.Background(), errFake("random error"))) } func TestIGProvider_OnReauthorization_Cov7(t *testing.T) { p := NewInstagramProvider() err := p.OnReauthorization(context.Background(), &model.Inbox{Base: model.Base{ID: 1}}) require.NoError(t, err) } // =========================== // InstagramProvider — Convenience methods (Cov7) // =========================== func TestIGProvider_CreateChannel_Success_Cov7(t *testing.T) { srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "application/json") mustEncodeTestJSON(w, IGUserProfile{ID: "12345", Username: "testuser"}) })) defer srv.Close() p := NewInstagramProvider() p.graphAPIBase = srv.URL ch, err := p.CreateChannel(context.Background(), 10, channel.ChannelConfig{ "instagram_account_id": "12345", "page_access_token": "tok", }) require.NoError(t, err) assert.NotNil(t, ch) } func TestIGProvider_CreateChannel_APIError_Cov7(t *testing.T) { srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusUnauthorized) })) defer srv.Close() p := NewInstagramProvider() p.graphAPIBase = srv.URL _, err := p.CreateChannel(context.Background(), 10, channel.ChannelConfig{ "instagram_account_id": "12345", "page_access_token": "tok", }) require.Error(t, err) } func TestIGProvider_UpdateChannel_Cov7(t *testing.T) { p := NewInstagramProvider() ch, err := p.UpdateChannel(context.Background(), 1, channel.ChannelConfig{ "page_access_token": "new_tok", "instagram_account_name": "newname", }) require.NoError(t, err) assert.NotNil(t, ch) } func TestIGProvider_DeleteChannel_Cov7(t *testing.T) { p := NewInstagramProvider() err := p.DeleteChannel(context.Background(), 1) require.NoError(t, err) } func TestIGProvider_HandleWebhook_Instagram_Cov7(t *testing.T) { p := NewInstagramProvider() err := p.HandleWebhook(context.Background(), map[string]interface{}{ "object": "instagram", "entry": []interface{}{}, }) require.NoError(t, err) } func TestIGProvider_HandleWebhook_NonInstagram_Cov7(t *testing.T) { p := NewInstagramProvider() err := p.HandleWebhook(context.Background(), map[string]interface{}{ "object": "page", "entry": []interface{}{}, }) require.NoError(t, err) } func TestIGProvider_HandleWebhook_BadPayload_Cov7(t *testing.T) { p := NewInstagramProvider() err := p.HandleWebhook(context.Background(), map[string]interface{}{ "object": func() {}, }) require.Error(t, err) } func TestIGProvider_ProcessIncomingMessage_Cov7(t *testing.T) { p := NewInstagramProvider() inbox := &model.Inbox{Base: model.Base{ID: 1}, AccountID: 10} payload := map[string]interface{}{ "object": "instagram", "entry": []interface{}{ map[string]interface{}{ "id": "123", "time": int64(1234567890), "messaging": []interface{}{ map[string]interface{}{ "sender": map[string]interface{}{"id": "s1"}, "recipient": map[string]interface{}{"id": "r1"}, "timestamp": int64(1234567890), "message": map[string]interface{}{"mid": "m1", "text": "ig hello"}, }, }, }, }, } msg, err := p.ProcessIncomingMessage(context.Background(), inbox, payload) require.NoError(t, err) assert.Equal(t, "ig hello", msg.Content) } // =========================== // InstagramProvider — Sender actions & helpers (Cov7) // =========================== func TestIGProvider_SendTypingOn_Cov7(t *testing.T) { srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { if _, err := w.Write([]byte(`{"success":true}`)); err != nil { panic(err) } })) defer srv.Close() p := NewInstagramProvider() p.graphAPIBase = srv.URL err := p.SendTypingOn(context.Background(), "12345", "tok") require.NoError(t, err) } func TestIGProvider_SendTypingOff_Cov7(t *testing.T) { srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { if _, err := w.Write([]byte(`{"success":true}`)); err != nil { panic(err) } })) defer srv.Close() p := NewInstagramProvider() p.graphAPIBase = srv.URL err := p.SendTypingOff(context.Background(), "12345", "tok") require.NoError(t, err) } func TestIGProvider_SendTypingOn_APIError_Cov7(t *testing.T) { srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusInternalServerError) })) defer srv.Close() p := NewInstagramProvider() p.graphAPIBase = srv.URL err := p.SendTypingOn(context.Background(), "12345", "tok") require.Error(t, err) } func TestIGProvider_ExtractRecipientIGID_Numeric_Cov7(t *testing.T) { p := NewInstagramProvider() contact := &model.Contact{Identifier: "12345"} assert.Equal(t, "12345", p.extractRecipientIGID(contact)) } func TestIGProvider_ExtractRecipientIGID_NonNumeric_Cov7(t *testing.T) { p := NewInstagramProvider() contact := &model.Contact{Identifier: "abc"} assert.Empty(t, p.extractRecipientIGID(contact)) } func TestIGProvider_GetPageAccessTokenFromInbox_Cov7(t *testing.T) { p := NewInstagramProvider() inbox := &model.Inbox{Base: model.Base{ID: 1}, ChannelConfig: `{"page_access_token":"my_token"}`} assert.Equal(t, "my_token", p.GetPageAccessTokenFromInbox(inbox)) } func TestIGProvider_GetPageAccessTokenFromInbox_Empty_Cov7(t *testing.T) { p := NewInstagramProvider() inbox := &model.Inbox{Base: model.Base{ID: 1}, ChannelConfig: ""} assert.Empty(t, p.GetPageAccessTokenFromInbox(inbox)) } func TestIGProvider_MapContentTypeToIGAttachmentType_Cov7(t *testing.T) { p := NewInstagramProvider() assert.Equal(t, "image", p.mapContentTypeToIGAttachmentType("image")) assert.Equal(t, "video", p.mapContentTypeToIGAttachmentType("video")) assert.Equal(t, "audio", p.mapContentTypeToIGAttachmentType("audio")) assert.Equal(t, "file", p.mapContentTypeToIGAttachmentType("file")) assert.Equal(t, "file", p.mapContentTypeToIGAttachmentType("unknown")) } // =========================== // InstagramProvider — Comment Management API (Cov7) // =========================== func TestIGProvider_GetComments_NoToken_Cov7(t *testing.T) { t.Skip("network test") p := NewInstagramProvider() _, err := p.GetComments(context.Background(), "", "media_1", 10, "") require.Error(t, err) } func TestIGProvider_GetComments_NoMediaID_Cov7(t *testing.T) { t.Skip("network test") p := NewInstagramProvider() _, err := p.GetComments(context.Background(), "tok", "", 10, "") require.Error(t, err) } func TestIGProvider_GetComments_Success_Cov7(t *testing.T) { t.Skip("network test") srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "application/json") if _, err := w.Write([]byte(`{"data":[]}`)); err != nil { panic(err) } })) defer srv.Close() p := NewInstagramProvider() p.graphAPIBase = srv.URL result, err := p.GetComments(context.Background(), "tok", "media_1", 10, "cursor1") require.NoError(t, err) assert.NotNil(t, result) } func TestIGProvider_GetComments_APIError_Cov7(t *testing.T) { t.Skip("network test") srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusInternalServerError) })) defer srv.Close() p := NewInstagramProvider() p.graphAPIBase = srv.URL _, err := p.GetComments(context.Background(), "tok", "media_1", 0, "") require.Error(t, err) } func TestIGProvider_GetCommentReplies_NoToken_Cov7(t *testing.T) { p := NewInstagramProvider() _, err := p.GetCommentReplies(context.Background(), "", "comment_1", 10, "") require.Error(t, err) } func TestIGProvider_GetCommentReplies_NoCommentID_Cov7(t *testing.T) { p := NewInstagramProvider() _, err := p.GetCommentReplies(context.Background(), "tok", "", 10, "") require.Error(t, err) } func TestIGProvider_GetCommentReplies_Success_Cov7(t *testing.T) { srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "application/json") if _, err := w.Write([]byte(`{"data":[]}`)); err != nil { panic(err) } })) defer srv.Close() p := NewInstagramProvider() p.graphAPIBase = srv.URL result, err := p.GetCommentReplies(context.Background(), "tok", "comment_1", 10, "") require.NoError(t, err) assert.NotNil(t, result) } func TestIGProvider_ReplyToComment_NoToken_Cov7(t *testing.T) { t.Skip("network test") p := NewInstagramProvider() _, err := p.ReplyToComment(context.Background(), "", "comment_1", "reply text") require.Error(t, err) } func TestIGProvider_ReplyToComment_NoCommentID_Cov7(t *testing.T) { t.Skip("network test") p := NewInstagramProvider() _, err := p.ReplyToComment(context.Background(), "tok", "", "reply text") require.Error(t, err) } func TestIGProvider_ReplyToComment_NoMessage_Cov7(t *testing.T) { t.Skip("network test") p := NewInstagramProvider() _, err := p.ReplyToComment(context.Background(), "tok", "comment_1", "") require.Error(t, err) } func TestIGProvider_ReplyToComment_Success_Cov7(t *testing.T) { t.Skip("network test") srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "application/json") if _, err := w.Write([]byte(`{"id":"reply_1"}`)); err != nil { panic(err) } })) defer srv.Close() p := NewInstagramProvider() p.graphAPIBase = srv.URL result, err := p.ReplyToComment(context.Background(), "tok", "comment_1", "reply text") require.NoError(t, err) assert.Equal(t, "reply_1", result.ID) } func TestIGProvider_ReplyToComment_APIError_Cov7(t *testing.T) { t.Skip("network test") srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusInternalServerError) })) defer srv.Close() p := NewInstagramProvider() p.graphAPIBase = srv.URL _, err := p.ReplyToComment(context.Background(), "tok", "comment_1", "reply text") require.Error(t, err) } func TestIGProvider_HideComment_NoToken_Cov7(t *testing.T) { t.Skip("network test") p := NewInstagramProvider() _, err := p.HideComment(context.Background(), "", "comment_1", true) require.Error(t, err) } func TestIGProvider_HideComment_NoCommentID_Cov7(t *testing.T) { t.Skip("network test") p := NewInstagramProvider() _, err := p.HideComment(context.Background(), "tok", "", true) require.Error(t, err) } func TestIGProvider_HideComment_Success_Cov7(t *testing.T) { t.Skip("network test") srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "application/json") if _, err := w.Write([]byte(`{"success":true}`)); err != nil { panic(err) } })) defer srv.Close() p := NewInstagramProvider() p.graphAPIBase = srv.URL result, err := p.HideComment(context.Background(), "tok", "comment_1", true) require.NoError(t, err) assert.True(t, result.Success) } func TestIGProvider_HideComment_APIError_Cov7(t *testing.T) { t.Skip("network test") srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusInternalServerError) })) defer srv.Close() p := NewInstagramProvider() p.graphAPIBase = srv.URL _, err := p.HideComment(context.Background(), "tok", "comment_1", false) require.Error(t, err) } func TestIGProvider_DeleteComment_NoToken_Cov7(t *testing.T) { t.Skip("network test") p := NewInstagramProvider() _, err := p.DeleteComment(context.Background(), "", "comment_1") require.Error(t, err) } func TestIGProvider_DeleteComment_NoCommentID_Cov7(t *testing.T) { t.Skip("network test") p := NewInstagramProvider() _, err := p.DeleteComment(context.Background(), "tok", "") require.Error(t, err) } func TestIGProvider_DeleteComment_Success_Cov7(t *testing.T) { t.Skip("network test") srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "application/json") if _, err := w.Write([]byte(`{"success":true}`)); err != nil { panic(err) } })) defer srv.Close() p := NewInstagramProvider() p.graphAPIBase = srv.URL result, err := p.DeleteComment(context.Background(), "tok", "comment_1") require.NoError(t, err) assert.True(t, result.Success) } func TestIGProvider_DeleteComment_APIError_Cov7(t *testing.T) { t.Skip("network test") srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusInternalServerError) })) defer srv.Close() p := NewInstagramProvider() p.graphAPIBase = srv.URL _, err := p.DeleteComment(context.Background(), "tok", "comment_1") require.Error(t, err) } func TestIGProvider_ProcessCommentIncoming_Nil_Cov7(t *testing.T) { p := NewInstagramProvider() _, err := p.ProcessCommentIncoming(context.Background(), &model.Inbox{Base: model.Base{ID: 1}}, nil, EventIGComment) require.Error(t, err) } func TestIGProvider_ProcessCommentIncoming_Comment_Cov7(t *testing.T) { p := NewInstagramProvider() commentData := &IGCommentChangeValue{ CommentID: "c1", MediaID: "m1", Text: "hello", From: IGCommentFrom{ID: "u1", Username: "user1"}, } msg, err := p.ProcessCommentIncoming(context.Background(), &model.Inbox{Base: model.Base{ID: 1}}, commentData, EventIGComment) require.NoError(t, err) assert.Equal(t, "hello", msg.Content) } func TestIGProvider_ProcessCommentIncoming_Reply_Cov7(t *testing.T) { p := NewInstagramProvider() commentData := &IGCommentChangeValue{ CommentID: "c2", MediaID: "m1", Text: "reply text", ParentID: "c1", From: IGCommentFrom{ID: "u1", Username: "user1"}, } msg, err := p.ProcessCommentIncoming(context.Background(), &model.Inbox{Base: model.Base{ID: 1}}, commentData, EventIGCommentReply) require.NoError(t, err) assert.Equal(t, "reply text", msg.Content) } func TestIGProvider_ProcessCommentIncoming_Deleted_Cov7(t *testing.T) { p := NewInstagramProvider() commentData := &IGCommentChangeValue{ CommentID: "c3", MediaID: "m1", Text: "", IsDeleted: true, From: IGCommentFrom{ID: "u1", Username: "user1"}, } msg, err := p.ProcessCommentIncoming(context.Background(), &model.Inbox{Base: model.Base{ID: 1}}, commentData, EventIGCommentDeleted) require.NoError(t, err) assert.Contains(t, msg.Content, "deleted") } // =========================== // InstagramProvider — Webhook Registration (Cov7) // =========================== func TestIGProvider_RegisterWebhook_Success_Cov7(t *testing.T) { t.Skip("network test") srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "application/json") if _, err := w.Write([]byte(`{"success":true}`)); err != nil { panic(err) } })) defer srv.Close() p := NewInstagramProvider() p.graphAPIBase = srv.URL result, err := p.RegisterWebhook(context.Background(), "tok", "page_1") require.NoError(t, err) assert.NotEmpty(t, result) } func TestIGProvider_RegisterWebhook_APIError_Cov7(t *testing.T) { t.Skip("network test") srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusInternalServerError) })) defer srv.Close() p := NewInstagramProvider() p.graphAPIBase = srv.URL _, err := p.RegisterWebhook(context.Background(), "tok", "page_1") require.Error(t, err) } func TestIGProvider_RegisterWebhook_SuccessFalse_Cov7(t *testing.T) { t.Skip("network test") srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "application/json") if _, err := w.Write([]byte(`{"success":false}`)); err != nil { panic(err) } })) defer srv.Close() p := NewInstagramProvider() p.graphAPIBase = srv.URL _, err := p.RegisterWebhook(context.Background(), "tok", "page_1") require.Error(t, err) } func TestIGProvider_ListWebhooks_Success_Cov7(t *testing.T) { srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "application/json") if _, err := w.Write([]byte(`[]`)); err != nil { panic(err) } })) defer srv.Close() p := NewInstagramProvider() p.graphAPIBase = srv.URL result, err := p.ListWebhooks(context.Background(), "tok", "page_1") require.NoError(t, err) assert.NotNil(t, result) } func TestIGProvider_ListWebhooks_APIError_Cov7(t *testing.T) { srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusInternalServerError) })) defer srv.Close() p := NewInstagramProvider() p.graphAPIBase = srv.URL _, err := p.ListWebhooks(context.Background(), "tok", "page_1") require.Error(t, err) } func TestIGProvider_DeleteWebhook_Success_Cov7(t *testing.T) { srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusOK) })) defer srv.Close() p := NewInstagramProvider() p.graphAPIBase = srv.URL err := p.DeleteWebhook(context.Background(), "tok", "page_1") require.NoError(t, err) } func TestIGProvider_DeleteWebhook_APIError_Cov7(t *testing.T) { srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusInternalServerError) })) defer srv.Close() p := NewInstagramProvider() p.graphAPIBase = srv.URL err := p.DeleteWebhook(context.Background(), "tok", "page_1") require.Error(t, err) } // =========================== // Type instantiation tests (Cov7) // =========================== func TestFBTypes_Instantiation_Cov7(t *testing.T) { evt := FBWebhookEvent{Object: "page"} assert.Equal(t, "page", evt.Object) msg := FBMessageData{Mid: "m1", Text: "hello"} assert.Equal(t, "m1", msg.Mid) att := FBAttachment{Type: "image", Payload: FBAttachmentPayload{URL: "http://example.com"}} assert.Equal(t, "image", att.Type) profile := FBUserProfile{ID: "123", Name: "Test"} assert.Equal(t, "123", profile.ID) pageInfo := FBPageInfo{ID: "1", Name: "Page"} assert.Equal(t, "1", pageInfo.ID) tokenResp := FBLongLivedTokenResponse{AccessToken: "tok"} assert.Equal(t, "tok", tokenResp.AccessToken) igProfile := IGUserProfile{ID: "ig1", Username: "user"} assert.Equal(t, "ig1", igProfile.ID) commentVal := IGCommentChangeValue{CommentID: "c1", Text: "hello"} assert.Equal(t, "c1", commentVal.CommentID) paging := FBGraphPaging{Next: "http://next"} assert.Equal(t, "http://next", paging.Next) } func TestFBTypes_SendRequest_Cov7(t *testing.T) { req := FBSendMessageRequest{ Recipient: FBRecipient{ID: "123"}, MessagingType: "RESP", Message: &FBSendMessage{Text: "hello"}, } assert.Equal(t, "123", req.Recipient.ID) assert.Equal(t, "hello", req.Message.Text) } func TestFBTypes_SendAttachment_Cov7(t *testing.T) { att := FBSendAttachment{ Type: "image", Payload: FBSendAttachmentPayload{URL: "http://example.com/img.jpg"}, } assert.Equal(t, "image", att.Type) assert.Equal(t, "http://example.com/img.jpg", att.Payload.URL) } func TestFBTypes_SubscriptionVerify_Cov7(t *testing.T) { verify := FBWebhookSubscriptionVerify{Mode: "subscribe", VerifyToken: "tok", Challenge: "challenge"} assert.Equal(t, "subscribe", verify.Mode) } // errFake creates a simple error for testing type fakeError7 string func (e fakeError7) Error() string { return string(e) } func errFake(s string) error { return fakeError7(s) }