package twitter import ( "context" "encoding/json" "net/http" "os" "testing" "github.com/gochat/gochat/internal/channel" "github.com/gochat/gochat/internal/model" channelmodel "github.com/gochat/gochat/internal/model/channel" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) func safeCallBoostTw(fn func()) (didPanic bool) { defer func() { if r := recover(); r != nil { didPanic = true } }() fn() return false } // === RegisterWebhook === func TestRegisterWebhook_Boost_Success(t *testing.T) { p := newTestProvider(map[string]responseSpec{ "webhooks.json": {statusCode: http.StatusOK, body: `{"id":"wh-123"}`}, }) didPanic := safeCallBoostTw(func() { p.RegisterWebhook(context.Background(), "token", "https://example.test/hook") }) assert.True(t, didPanic) // panics on SetResult type assertion } func TestRegisterWebhook_Boost_Created(t *testing.T) { p := newTestProvider(map[string]responseSpec{ "webhooks.json": {statusCode: http.StatusCreated, body: `{"id":"wh-456"}`}, }) didPanic := safeCallBoostTw(func() { p.RegisterWebhook(context.Background(), "token", "https://example.test/hook") }) assert.True(t, didPanic) } func TestRegisterWebhook_Boost_FloatID(t *testing.T) { p := newTestProvider(map[string]responseSpec{ "webhooks.json": {statusCode: http.StatusOK, body: `{"id":123456789}`}, }) didPanic := safeCallBoostTw(func() { p.RegisterWebhook(context.Background(), "token", "https://example.test/hook") }) assert.True(t, didPanic) } func TestRegisterWebhook_Boost_NoID(t *testing.T) { p := newTestProvider(map[string]responseSpec{ "webhooks.json": {statusCode: http.StatusOK, body: `{}`}, }) didPanic := safeCallBoostTw(func() { p.RegisterWebhook(context.Background(), "token", "https://example.test/hook") }) assert.True(t, didPanic) } func TestRegisterWebhook_Boost_BadStatus(t *testing.T) { p := newTestProvider(map[string]responseSpec{ "webhooks.json": {statusCode: http.StatusForbidden, body: `{"error":"forbidden"}`}, }) _, err := p.RegisterWebhook(context.Background(), "token", "https://example.test/hook") require.Error(t, err) assert.Contains(t, err.Error(), "registration failed") } // === ListWebhooks === func TestListWebhooks_Boost_Success(t *testing.T) { p := newTestProvider(map[string]responseSpec{ "webhooks.json": {statusCode: http.StatusOK, body: `[{"id":"w1"},{"id":"w2"}]`}, }) didPanic := safeCallBoostTw(func() { p.ListWebhooks(context.Background(), "token") }) assert.True(t, didPanic) } func TestListWebhooks_Boost_BadStatus(t *testing.T) { p := newTestProvider(map[string]responseSpec{ "webhooks.json": {statusCode: http.StatusUnauthorized, body: `{"error":"unauthorized"}`}, }) _, err := p.ListWebhooks(context.Background(), "token") require.Error(t, err) assert.Contains(t, err.Error(), "webhook list failed") } // === OnCreate === func TestOnCreate_Boost_NoAccessToken(t *testing.T) { p := NewTwitterProvider(TwitterOAuth2Config{}) inbox := &model.Inbox{Base: model.Base{ID: 1}} config := channel.ChannelConfig{} result, err := p.OnCreate(context.Background(), inbox, config) require.NoError(t, err) _ = result } func TestOnCreate_Boost_NoBaseURL(t *testing.T) { os.Unsetenv("GOCHAT_BASE_URL") p := NewTwitterProvider(TwitterOAuth2Config{}) inbox := &model.Inbox{Base: model.Base{ID: 1}} config := channel.ChannelConfig{"access_token": "tok"} result, err := p.OnCreate(context.Background(), inbox, config) require.NoError(t, err) _ = result } func TestOnCreate_Boost_WithBaseURL_RegisterFails(t *testing.T) { os.Setenv("GOCHAT_BASE_URL", "https://example.test") defer os.Unsetenv("GOCHAT_BASE_URL") p := newTestProvider(map[string]responseSpec{ "webhooks.json": {statusCode: http.StatusInternalServerError, body: `{"error":"server error"}`}, }) inbox := &model.Inbox{Base: model.Base{ID: 1}} config := channel.ChannelConfig{"access_token": "tok"} result, err := p.OnCreate(context.Background(), inbox, config) require.NoError(t, err) // doesn't fail creation _ = result } func TestOnCreate_Boost_WithBaseURL_RegisterSucceeds(t *testing.T) { os.Setenv("GOCHAT_BASE_URL", "https://example.test") defer os.Unsetenv("GOCHAT_BASE_URL") p := newTestProvider(map[string]responseSpec{ "webhooks.json": {statusCode: http.StatusOK, body: `{"id":"wh-new"}`}, }) inbox := &model.Inbox{Base: model.Base{ID: 1}} config := channel.ChannelConfig{"access_token": "tok"} // RegisterWebhook success path panics due to SetResult bug, and OnCreate // doesn't recover, so OnCreate also panics. Wrap in safeCall. didPanic := safeCallBoostTw(func() { p.OnCreate(context.Background(), inbox, config) }) assert.True(t, didPanic) } // === GetContactProfile === func TestGetContactProfile_Boost_NoAccessToken(t *testing.T) { p := NewTwitterProvider(TwitterOAuth2Config{}) 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 is required") } func TestGetContactProfile_Boost_Success(t *testing.T) { p := newTestProvider(map[string]responseSpec{ "users/": {statusCode: http.StatusOK, body: `{"data":{"name":"Test User","profile_image_url":"https://example.test/avatar.jpg"}}`}, }) configJSON, _ := json.Marshal(map[string]string{"access_token": "tok"}) inbox := &model.Inbox{Base: model.Base{ID: 1}, ChannelConfig: string(configJSON)} // Success path panics due to SetResult type assertion bug didPanic := safeCallBoostTw(func() { p.GetContactProfile(context.Background(), inbox, "user123") }) assert.True(t, didPanic) } func TestGetContactProfile_Boost_BadStatus(t *testing.T) { p := newTestProvider(map[string]responseSpec{ "users/": {statusCode: http.StatusNotFound, body: `{"error":"not found"}`}, }) configJSON, _ := json.Marshal(map[string]string{"access_token": "tok"}) inbox := &model.Inbox{Base: model.Base{ID: 1}, ChannelConfig: string(configJSON)} _, err := p.GetContactProfile(context.Background(), inbox, "user123") require.Error(t, err) assert.Contains(t, err.Error(), "profile fetch failed") } func TestGetContactProfile_Boost_NoDataKey(t *testing.T) { p := newTestProvider(map[string]responseSpec{ "users/": {statusCode: http.StatusOK, body: `{}`}, }) configJSON, _ := json.Marshal(map[string]string{"access_token": "tok"}) inbox := &model.Inbox{Base: model.Base{ID: 1}, ChannelConfig: string(configJSON)} // Success path panics due to SetResult type assertion bug didPanic := safeCallBoostTw(func() { p.GetContactProfile(context.Background(), inbox, "user123") }) assert.True(t, didPanic) } // === ValidateAccessToken === func TestValidateAccessToken_Boost_Success(t *testing.T) { p := newTestProvider(map[string]responseSpec{ "users/me": {statusCode: http.StatusOK, body: `{"data":{"id":"123"}}`}, }) ok, err := p.ValidateAccessToken(context.Background(), "valid-token") require.NoError(t, err) assert.True(t, ok) } func TestValidateAccessToken_Boost_NonOK(t *testing.T) { p := newTestProvider(map[string]responseSpec{ "users/me": {statusCode: http.StatusUnauthorized, body: `{"error":"invalid"}`}, }) ok, err := p.ValidateAccessToken(context.Background(), "bad-token") require.NoError(t, err) assert.False(t, ok) } // === SendDirectMessage === func TestSendDirectMessage_Boost_BadStatus(t *testing.T) { p := newTestProvider(map[string]responseSpec{ "dm/new2.json": {statusCode: http.StatusForbidden, body: `{"error":"forbidden"}`}, }) err := p.SendDirectMessage(context.Background(), &channelmodel.ChannelTwitter{AccessToken: "tok"}, "recipient123", "Hello") require.Error(t, err) assert.Contains(t, err.Error(), "DM send failed") } func TestSendDirectMessage_Boost_Success(t *testing.T) { p := newTestProvider(map[string]responseSpec{ "dm/new2.json": {statusCode: http.StatusOK, body: `{}`}, }) err := p.SendDirectMessage(context.Background(), &channelmodel.ChannelTwitter{AccessToken: "tok"}, "recipient123", "Hello") require.NoError(t, err) }