284 lines
9.0 KiB
Go
284 lines
9.0 KiB
Go
package service
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"io"
|
|
"net/http"
|
|
"strings"
|
|
"testing"
|
|
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/require"
|
|
"gorm.io/driver/sqlite"
|
|
"gorm.io/gorm"
|
|
"gorm.io/gorm/logger"
|
|
|
|
"github.com/gochat/gochat/internal/model"
|
|
"github.com/gochat/gochat/internal/repository"
|
|
)
|
|
|
|
func setupSlackIntegrationServiceTestDB(t *testing.T) *gorm.DB {
|
|
t.Helper()
|
|
|
|
db, err := gorm.Open(sqlite.Open("file::memory:"), &gorm.Config{
|
|
Logger: logger.Default.LogMode(logger.Silent),
|
|
})
|
|
require.NoError(t, err, "failed to open SQLite test database")
|
|
|
|
require.NoError(t, db.AutoMigrate(
|
|
&model.Account{},
|
|
&model.IntegrationHook{},
|
|
&model.IntegrationApp{},
|
|
), "failed to auto-migrate models")
|
|
|
|
t.Cleanup(func() {
|
|
sqlDB, _ := db.DB()
|
|
sqlDB.Close()
|
|
})
|
|
|
|
return db
|
|
}
|
|
|
|
func setupSlackIntegrationService(t *testing.T) (*SlackIntegrationService, *gorm.DB) {
|
|
t.Helper()
|
|
db := setupSlackIntegrationServiceTestDB(t)
|
|
hookRepo := repository.NewIntegrationHookRepo(db)
|
|
svc := NewSlackIntegrationService(hookRepo)
|
|
return svc, db
|
|
}
|
|
|
|
type slackRoundTripFunc func(*http.Request) (*http.Response, error)
|
|
|
|
func (f slackRoundTripFunc) RoundTrip(req *http.Request) (*http.Response, error) {
|
|
return f(req)
|
|
}
|
|
|
|
func slackJSONResponse(body string) *http.Response {
|
|
return &http.Response{
|
|
StatusCode: http.StatusOK,
|
|
Header: make(http.Header),
|
|
Body: io.NopCloser(strings.NewReader(body)),
|
|
}
|
|
}
|
|
|
|
func setFakeSlackClient(svc *SlackIntegrationService, rt slackRoundTripFunc) {
|
|
svc.client = &slackAPIClient{baseURL: "https://slack.test/api", httpClient: &http.Client{Transport: rt}}
|
|
}
|
|
|
|
func seedSlackAccount(db *gorm.DB, t *testing.T) uint {
|
|
t.Helper()
|
|
account := &model.Account{Name: "Test Slack Account"}
|
|
require.NoError(t, db.Create(account).Error, "failed to seed account")
|
|
return account.ID
|
|
}
|
|
|
|
// ========================================
|
|
// SlackIntegrationService — Create tests
|
|
// ========================================
|
|
|
|
func TestSlackIntegrationService_Create(t *testing.T) {
|
|
svc, db := setupSlackIntegrationService(t)
|
|
accountID := seedSlackAccount(db, t)
|
|
t.Setenv("SLACK_CLIENT_ID", "client-id")
|
|
t.Setenv("SLACK_CLIENT_SECRET", "client-secret")
|
|
t.Setenv("FRONTEND_URL", "https://gochat.test")
|
|
setFakeSlackClient(svc, func(req *http.Request) (*http.Response, error) {
|
|
assert.Equal(t, "/api/oauth.v2.access", req.URL.Path)
|
|
assert.Equal(t, http.MethodPost, req.Method)
|
|
body, _ := io.ReadAll(req.Body)
|
|
values := string(body)
|
|
assert.Contains(t, values, "code=oauth-code")
|
|
assert.Contains(t, values, "redirect_uri=https%3A%2F%2Fgochat.test%2Fapp%2Faccounts%2F1%2Fsettings%2Fintegrations%2Fslack")
|
|
return slackJSONResponse(`{"ok":true,"access_token":"xoxb-oauth-token"}`), nil
|
|
})
|
|
|
|
req := CreateSlackRequest{
|
|
Code: "oauth-code",
|
|
}
|
|
|
|
hook, err := svc.Create(context.Background(), accountID, req)
|
|
assert.NoError(t, err)
|
|
assert.NotZero(t, hook.ID)
|
|
assert.Equal(t, "slack", hook.AppID)
|
|
assert.Equal(t, model.HookTypeSlack, hook.HookType)
|
|
assert.Equal(t, model.HookStatusInactive, hook.Status)
|
|
assert.Equal(t, accountID, hook.AccountID)
|
|
assert.Equal(t, "xoxb-oauth-token", hook.AccessToken)
|
|
assert.NotNil(t, hook.Settings)
|
|
}
|
|
|
|
func TestSlackIntegrationService_Create_SettingsStored(t *testing.T) {
|
|
svc, db := setupSlackIntegrationService(t)
|
|
accountID := seedSlackAccount(db, t)
|
|
|
|
req := CreateSlackRequest{
|
|
ChannelID: "C99999999",
|
|
ChannelName: "notifications",
|
|
SlackToken: "xoxb-special-token",
|
|
}
|
|
|
|
hook, err := svc.Create(context.Background(), accountID, req)
|
|
require.NoError(t, err)
|
|
|
|
// Verify that the settings JSON contains the Slack fields
|
|
var settings model.SlackSettings
|
|
err = json.Unmarshal(hook.Settings, &settings)
|
|
require.NoError(t, err, "failed to unmarshal settings JSON")
|
|
|
|
assert.Equal(t, "C99999999", settings.ChannelID)
|
|
assert.Equal(t, "notifications", settings.ChannelName)
|
|
assert.Equal(t, "xoxb-special-token", settings.SlackToken)
|
|
}
|
|
|
|
// ========================================
|
|
// SlackIntegrationService — Update tests
|
|
// ========================================
|
|
|
|
func TestSlackIntegrationService_Update(t *testing.T) {
|
|
svc, db := setupSlackIntegrationService(t)
|
|
accountID := seedSlackAccount(db, t)
|
|
|
|
// Create first
|
|
createReq := CreateSlackRequest{
|
|
ChannelID: "C10000001",
|
|
ChannelName: "old-channel",
|
|
SlackToken: "xoxb-old-token",
|
|
}
|
|
_, err := svc.Create(context.Background(), accountID, createReq)
|
|
require.NoError(t, err)
|
|
|
|
// Update
|
|
setFakeSlackClient(svc, func(req *http.Request) (*http.Response, error) {
|
|
switch req.URL.Path {
|
|
case "/api/conversations.list":
|
|
if req.URL.Query().Get("types") == "private_channel" {
|
|
return slackJSONResponse(`{"ok":true,"channels":[],"response_metadata":{"next_cursor":""}}`), nil
|
|
}
|
|
return slackJSONResponse(`{"ok":true,"channels":[{"id":"C20000002","name":"new-channel","is_private":false}],"response_metadata":{"next_cursor":""}}`), nil
|
|
case "/api/conversations.join":
|
|
return slackJSONResponse(`{"ok":true}`), nil
|
|
default:
|
|
t.Fatalf("unexpected Slack API path: %s", req.URL.Path)
|
|
}
|
|
return nil, nil
|
|
})
|
|
updateReq := UpdateSlackRequest{
|
|
ReferenceID: "C20000002",
|
|
}
|
|
|
|
updated, err := svc.Update(context.Background(), accountID, updateReq)
|
|
assert.NoError(t, err)
|
|
|
|
// Verify updated settings
|
|
var settings model.SlackSettings
|
|
err = json.Unmarshal(updated.Settings, &settings)
|
|
require.NoError(t, err)
|
|
assert.Equal(t, "", settings.ChannelID)
|
|
assert.Equal(t, "new-channel", settings.ChannelName)
|
|
assert.Equal(t, "", settings.SlackToken)
|
|
assert.Equal(t, "C20000002", updated.ReferenceID)
|
|
assert.Equal(t, model.HookStatusActive, updated.Status)
|
|
}
|
|
|
|
func TestSlackIntegrationService_Update_NotFound(t *testing.T) {
|
|
svc, db := setupSlackIntegrationService(t)
|
|
accountID := seedSlackAccount(db, t)
|
|
|
|
// No Slack hook created for this account — update should fail
|
|
updateReq := UpdateSlackRequest{
|
|
ChannelID: "C00000000",
|
|
}
|
|
|
|
updated, err := svc.Update(context.Background(), accountID, updateReq)
|
|
assert.Error(t, err)
|
|
assert.Nil(t, updated)
|
|
assert.Contains(t, err.Error(), "not found")
|
|
}
|
|
|
|
// ========================================
|
|
// SlackIntegrationService — Delete tests
|
|
// ========================================
|
|
|
|
func TestSlackIntegrationService_Delete(t *testing.T) {
|
|
svc, db := setupSlackIntegrationService(t)
|
|
accountID := seedSlackAccount(db, t)
|
|
|
|
// Create first
|
|
createReq := CreateSlackRequest{
|
|
ChannelID: "C30000003",
|
|
ChannelName: "delete-me",
|
|
SlackToken: "xoxb-delete-token",
|
|
}
|
|
_, err := svc.Create(context.Background(), accountID, createReq)
|
|
require.NoError(t, err)
|
|
|
|
// Delete
|
|
err = svc.Delete(context.Background(), accountID)
|
|
assert.NoError(t, err)
|
|
|
|
// Verify it's gone — no Slack hooks should remain for this account
|
|
hookRepo := repository.NewIntegrationHookRepo(db)
|
|
hooks, err := hookRepo.FindByAccountAndType(context.Background(), accountID, model.HookTypeSlack)
|
|
assert.NoError(t, err)
|
|
assert.Empty(t, hooks)
|
|
}
|
|
|
|
func TestSlackIntegrationService_Delete_NotFound(t *testing.T) {
|
|
svc, db := setupSlackIntegrationService(t)
|
|
accountID := seedSlackAccount(db, t)
|
|
|
|
// No Slack hook exists for this account — delete should fail
|
|
err := svc.Delete(context.Background(), accountID)
|
|
assert.Error(t, err)
|
|
assert.Contains(t, err.Error(), "not found")
|
|
}
|
|
|
|
// ========================================
|
|
// SlackIntegrationService — ListAllChannels tests
|
|
// ========================================
|
|
|
|
func TestSlackIntegrationService_ListAllChannels(t *testing.T) {
|
|
svc, db := setupSlackIntegrationService(t)
|
|
accountID := seedSlackAccount(db, t)
|
|
|
|
// Create a Slack integration first
|
|
createReq := CreateSlackRequest{
|
|
ChannelID: "C40000004",
|
|
ChannelName: "list-channels",
|
|
SlackToken: "xoxb-list-token",
|
|
}
|
|
_, err := svc.Create(context.Background(), accountID, createReq)
|
|
require.NoError(t, err)
|
|
setFakeSlackClient(svc, func(req *http.Request) (*http.Response, error) {
|
|
require.Equal(t, "/api/conversations.list", req.URL.Path)
|
|
require.Equal(t, "Bearer xoxb-list-token", req.Header.Get("Authorization"))
|
|
if req.URL.Query().Get("types") == "private_channel" {
|
|
return slackJSONResponse(`{"ok":true,"channels":[{"id":"G40000004","name":"private-room","is_private":true}],"response_metadata":{"next_cursor":""}}`), nil
|
|
}
|
|
return slackJSONResponse(`{"ok":true,"channels":[{"id":"C40000004","name":"list-channels","is_private":false}],"response_metadata":{"next_cursor":""}}`), nil
|
|
})
|
|
|
|
channels, err := svc.ListAllChannels(context.Background(), accountID)
|
|
assert.NoError(t, err)
|
|
assert.Len(t, channels, 2)
|
|
|
|
assert.Equal(t, "G40000004", channels[0]["id"])
|
|
assert.Equal(t, "private-room", channels[0]["name"])
|
|
assert.Equal(t, true, channels[0]["is_private"])
|
|
assert.Equal(t, "C40000004", channels[1]["id"])
|
|
assert.Equal(t, "list-channels", channels[1]["name"])
|
|
assert.Equal(t, false, channels[1]["is_private"])
|
|
}
|
|
|
|
func TestSlackIntegrationService_ListAllChannels_NotFound(t *testing.T) {
|
|
svc, db := setupSlackIntegrationService(t)
|
|
accountID := seedSlackAccount(db, t)
|
|
|
|
// No Slack integration exists for this account
|
|
channels, err := svc.ListAllChannels(context.Background(), accountID)
|
|
assert.Error(t, err)
|
|
assert.Nil(t, channels)
|
|
assert.Contains(t, err.Error(), "not found")
|
|
}
|