165 lines
7.7 KiB
Go
165 lines
7.7 KiB
Go
package v1
|
|
|
|
import (
|
|
"bytes"
|
|
"context"
|
|
"encoding/json"
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"testing"
|
|
|
|
"github.com/gin-gonic/gin"
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/require"
|
|
"gorm.io/driver/sqlite"
|
|
"gorm.io/gorm"
|
|
"gorm.io/gorm/logger"
|
|
|
|
facebookchannel "github.com/gochat/gochat/internal/channel/facebook"
|
|
"github.com/gochat/gochat/internal/model"
|
|
channelmodel "github.com/gochat/gochat/internal/model/channel"
|
|
"github.com/gochat/gochat/internal/repository"
|
|
"github.com/gochat/gochat/internal/service"
|
|
)
|
|
|
|
type fakeFacebookCallbackProvider struct {
|
|
longToken string
|
|
pages []facebookchannel.FBPageInfo
|
|
instagramID string
|
|
}
|
|
|
|
func (f *fakeFacebookCallbackProvider) ExchangeLongLivedUserToken(context.Context, string) (string, error) {
|
|
return f.longToken, nil
|
|
}
|
|
|
|
func (f *fakeFacebookCallbackProvider) ListFacebookPages(context.Context, string) ([]facebookchannel.FBPageInfo, error) {
|
|
return f.pages, nil
|
|
}
|
|
|
|
func (f *fakeFacebookCallbackProvider) FetchInstagramBusinessAccountID(context.Context, string) (string, error) {
|
|
return f.instagramID, nil
|
|
}
|
|
|
|
func setupFacebookCallbackTest(t *testing.T, provider *fakeFacebookCallbackProvider) (*gin.Engine, *gorm.DB) {
|
|
t.Helper()
|
|
gin.SetMode(gin.TestMode)
|
|
db, err := gorm.Open(sqlite.Open(":memory:"), &gorm.Config{Logger: logger.Default.LogMode(logger.Silent)})
|
|
require.NoError(t, err)
|
|
require.NoError(t, db.AutoMigrate(&model.Account{}, &model.Inbox{}, &model.WorkingHour{}, &channelmodel.ChannelFacebook{}))
|
|
|
|
inboxRepo := repository.NewInboxRepo(db)
|
|
fbRepo := repository.NewChannelFacebookRepo(db)
|
|
inboxSvc := service.NewInboxService(inboxRepo, nil, nil, nil, nil, nil, nil)
|
|
fbSvc := service.NewChannelFacebookService(fbRepo)
|
|
handler := NewFacebookChannelHandler(fbSvc, nil, inboxSvc, fbRepo, provider)
|
|
|
|
r := gin.New()
|
|
r.POST("/api/v1/accounts/:account_id/callbacks/register_facebook_page", handler.RegisterFacebookPage)
|
|
r.GET("/api/v1/accounts/:account_id/callbacks/register_facebook_page", handler.RegisterFacebookPage)
|
|
r.POST("/api/v1/accounts/:account_id/callbacks/facebook_pages", handler.FacebookPages)
|
|
r.POST("/api/v1/accounts/:account_id/callbacks/facebook_pages.json", handler.FacebookPages)
|
|
r.POST("/api/v1/accounts/:account_id/callbacks/reauthorize_page", handler.ReauthorizePage)
|
|
return r, db
|
|
}
|
|
|
|
func TestFacebookCallbacks_RegisterFacebookPage(t *testing.T) {
|
|
router, db := setupFacebookCallbackTest(t, &fakeFacebookCallbackProvider{instagramID: "ig-123"})
|
|
account := model.Account{Name: "Acme", Active: true}
|
|
require.NoError(t, db.Create(&account).Error)
|
|
|
|
body := map[string]any{"user_access_token": "user-token", "page_access_token": "page-token", "page_id": "page-1", "inbox_name": "Facebook"}
|
|
rec := performFacebookCallbackRequest(t, router, http.MethodPost, "/api/v1/accounts/1/callbacks/register_facebook_page", body)
|
|
require.Equal(t, http.StatusOK, rec.Code)
|
|
|
|
var payload map[string]any
|
|
require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &payload))
|
|
assert.Equal(t, "Facebook", payload["name"])
|
|
assert.Equal(t, "facebook", payload["channel_type"])
|
|
assert.Equal(t, "page-1", payload["page_id"])
|
|
|
|
var channel channelmodel.ChannelFacebook
|
|
require.NoError(t, db.First(&channel).Error)
|
|
assert.Equal(t, account.ID, channel.AccountID)
|
|
assert.Equal(t, "user-token", channel.UserAccessToken)
|
|
assert.Equal(t, "page-token", channel.PageAccessToken)
|
|
assert.Equal(t, "ig-123", channel.InstagramBusinessAccountID)
|
|
}
|
|
|
|
func TestFacebookCallbacks_FacebookPagesMarksExistingPages(t *testing.T) {
|
|
provider := &fakeFacebookCallbackProvider{longToken: "long-user-token", pages: []facebookchannel.FBPageInfo{{ID: "page-1", Name: "Existing", AccessToken: "token-1"}, {ID: "page-2", Name: "New", AccessToken: "token-2"}}}
|
|
router, db := setupFacebookCallbackTest(t, provider)
|
|
account := model.Account{Name: "Acme", Active: true}
|
|
require.NoError(t, db.Create(&account).Error)
|
|
require.NoError(t, db.Create(&channelmodel.ChannelFacebook{AccountID: account.ID, InboxID: 10, PageID: "page-1", PageAccessToken: "old"}).Error)
|
|
|
|
rec := performFacebookCallbackRequest(t, router, http.MethodPost, "/api/v1/accounts/1/callbacks/facebook_pages.json", map[string]any{"omniauth_token": "short-token"})
|
|
require.Equal(t, http.StatusOK, rec.Code)
|
|
|
|
var payload struct {
|
|
Data struct {
|
|
UserAccessToken string `json:"user_access_token"`
|
|
PageDetails []struct {
|
|
ID string `json:"id"`
|
|
Exists bool `json:"exists"`
|
|
} `json:"page_details"`
|
|
} `json:"data"`
|
|
}
|
|
require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &payload))
|
|
assert.Equal(t, "long-user-token", payload.Data.UserAccessToken)
|
|
require.Len(t, payload.Data.PageDetails, 2)
|
|
assert.True(t, payload.Data.PageDetails[0].Exists)
|
|
assert.False(t, payload.Data.PageDetails[1].Exists)
|
|
}
|
|
|
|
func TestFacebookCallbacks_ReauthorizePageUpdatesMatchingPage(t *testing.T) {
|
|
provider := &fakeFacebookCallbackProvider{longToken: "long-user-token", instagramID: "ig-456", pages: []facebookchannel.FBPageInfo{{ID: "page-1", Name: "Renamed", AccessToken: "new-page-token"}}}
|
|
router, db := setupFacebookCallbackTest(t, provider)
|
|
account := model.Account{Name: "Acme", Active: true}
|
|
require.NoError(t, db.Create(&account).Error)
|
|
inbox := createTestChannelInbox(t, db, account.ID, 0, "Facebook", "facebook", map[string]any{"page_id": "page-1", "page_access_token": "old-page-token"})
|
|
channel := channelmodel.ChannelFacebook{AccountID: account.ID, InboxID: inbox.ID, PageID: "page-1", PageAccessToken: "old-page-token", ReauthorizationRequired: true}
|
|
require.NoError(t, db.Create(&channel).Error)
|
|
inbox.ChannelID = channel.ID
|
|
require.NoError(t, db.Save(inbox).Error)
|
|
|
|
rec := performFacebookCallbackRequest(t, router, http.MethodPost, "/api/v1/accounts/1/callbacks/reauthorize_page", map[string]any{"omniauth_token": "short-token", "inbox_id": inbox.ID})
|
|
require.Equal(t, http.StatusOK, rec.Code)
|
|
|
|
var updated channelmodel.ChannelFacebook
|
|
require.NoError(t, db.First(&updated, channel.ID).Error)
|
|
assert.Equal(t, "long-user-token", updated.UserAccessToken)
|
|
assert.Equal(t, "new-page-token", updated.PageAccessToken)
|
|
assert.Equal(t, "Renamed", updated.PageName)
|
|
assert.Equal(t, "ig-456", updated.InstagramBusinessAccountID)
|
|
assert.False(t, updated.ReauthorizationRequired)
|
|
|
|
var updatedInbox model.Inbox
|
|
require.NoError(t, db.First(&updatedInbox, inbox.ID).Error)
|
|
var config map[string]any
|
|
require.NoError(t, json.Unmarshal([]byte(updatedInbox.ChannelConfig), &config))
|
|
assert.Equal(t, "new-page-token", config["page_access_token"])
|
|
}
|
|
|
|
func TestFacebookCallbacks_ReauthorizePageReturns422WhenPageMissing(t *testing.T) {
|
|
provider := &fakeFacebookCallbackProvider{longToken: "long-user-token", pages: []facebookchannel.FBPageInfo{{ID: "other-page", AccessToken: "token"}}}
|
|
router, db := setupFacebookCallbackTest(t, provider)
|
|
account := model.Account{Name: "Acme", Active: true}
|
|
require.NoError(t, db.Create(&account).Error)
|
|
inbox := createTestChannelInbox(t, db, account.ID, 0, "Facebook", "facebook", map[string]any{"page_id": "page-1"})
|
|
require.NoError(t, db.Create(&channelmodel.ChannelFacebook{AccountID: account.ID, InboxID: inbox.ID, PageID: "page-1", PageAccessToken: "old"}).Error)
|
|
|
|
rec := performFacebookCallbackRequest(t, router, http.MethodPost, "/api/v1/accounts/1/callbacks/reauthorize_page", map[string]any{"omniauth_token": "short-token", "inbox_id": inbox.ID})
|
|
assert.Equal(t, http.StatusUnprocessableEntity, rec.Code)
|
|
}
|
|
|
|
func performFacebookCallbackRequest(t *testing.T, router *gin.Engine, method, path string, body map[string]any) *httptest.ResponseRecorder {
|
|
t.Helper()
|
|
encoded, err := json.Marshal(body)
|
|
require.NoError(t, err)
|
|
req := httptest.NewRequest(method, path, bytes.NewReader(encoded))
|
|
req.Header.Set("Content-Type", "application/json")
|
|
rec := httptest.NewRecorder()
|
|
router.ServeHTTP(rec, req)
|
|
return rec
|
|
}
|