Files
Memoh/internal/schedule/service_integration_test.go
T
2026-03-15 00:58:38 +08:00

157 lines
4.5 KiB
Go

package schedule_test
import (
"context"
"encoding/json"
"log/slog"
"os"
"strings"
"testing"
"github.com/jackc/pgx/v5/pgtype"
"github.com/jackc/pgx/v5/pgxpool"
"github.com/memohai/memoh/internal/boot"
"github.com/memohai/memoh/internal/db"
"github.com/memohai/memoh/internal/db/sqlc"
"github.com/memohai/memoh/internal/schedule"
)
func setupScheduleIntegrationTest(t *testing.T) (*schedule.Service, *sqlc.Queries, *pgxpool.Pool, *mockTriggerer, func()) {
t.Helper()
dsn := os.Getenv("TEST_POSTGRES_DSN")
if dsn == "" {
t.Skip("skip integration test: TEST_POSTGRES_DSN is not set")
}
ctx := context.Background()
pool, err := pgxpool.New(ctx, dsn)
if err != nil {
t.Skipf("skip integration test: cannot connect to database: %v", err)
}
if err := pool.Ping(ctx); err != nil {
pool.Close()
t.Skipf("skip integration test: database ping failed: %v", err)
}
queries := sqlc.New(pool)
mock := &mockTriggerer{}
logger := slog.New(slog.NewTextHandler(os.Stdout, &slog.HandlerOptions{Level: slog.LevelDebug}))
cfg := &boot.RuntimeConfig{JwtSecret: "integration-test-jwt-secret"}
svc := schedule.NewService(logger, queries, mock, cfg)
return svc, queries, pool, mock, func() { pool.Close() }
}
type mockTriggerer struct {
called bool
botID string
payload schedule.TriggerPayload
token string
}
func (m *mockTriggerer) TriggerSchedule(_ context.Context, botID string, payload schedule.TriggerPayload, token string) error {
m.called = true
m.botID = botID
m.payload = payload
m.token = token
return nil
}
func createUserBotAndSchedule(ctx context.Context, t *testing.T, queries *sqlc.Queries) (ownerUserID, botID, scheduleID string) {
t.Helper()
userRow, err := queries.CreateUser(ctx, sqlc.CreateUserParams{
IsActive: true,
Metadata: []byte("{}"),
})
if err != nil {
t.Fatalf("create user: %v", err)
}
ownerUserID = userRow.ID.String()
pgOwnerID, err := db.ParseUUID(ownerUserID)
if err != nil {
t.Fatalf("parse owner uuid: %v", err)
}
meta, _ := json.Marshal(map[string]any{"source": "schedule-integration-test"})
botRow, err := queries.CreateBot(ctx, sqlc.CreateBotParams{
OwnerUserID: pgOwnerID,
DisplayName: pgtype.Text{String: "schedule-test-bot", Valid: true},
AvatarUrl: pgtype.Text{},
IsActive: true,
Metadata: meta,
Status: "ready",
})
if err != nil {
t.Fatalf("create bot: %v", err)
}
botID = botRow.ID.String()
pgBotID, err := db.ParseUUID(botID)
if err != nil {
t.Fatalf("parse bot uuid: %v", err)
}
schedRow, err := queries.CreateSchedule(ctx, sqlc.CreateScheduleParams{
Name: "integration-daily",
Description: "daily job for integration test",
Pattern: "0 0 * * *",
MaxCalls: pgtype.Int4{Valid: false},
Enabled: true,
Command: "run daily report",
BotID: pgBotID,
})
if err != nil {
t.Fatalf("create schedule: %v", err)
}
scheduleID = schedRow.ID.String()
return ownerUserID, botID, scheduleID
}
func cleanupScheduleTestData(ctx context.Context, t *testing.T, queries *sqlc.Queries, pool *pgxpool.Pool, ownerUserID, botID, scheduleID string) {
t.Helper()
schedID, _ := db.ParseUUID(scheduleID)
_ = queries.DeleteSchedule(ctx, schedID)
botUUID, _ := db.ParseUUID(botID)
_ = queries.DeleteBotByID(ctx, botUUID)
userUUID, _ := db.ParseUUID(ownerUserID)
_, _ = pool.Exec(ctx, "DELETE FROM users WHERE id = $1", userUUID)
}
func TestIntegrationTrigger_CallsTriggererWithCorrectPayload(t *testing.T) {
svc, queries, pool, mock, cleanup := setupScheduleIntegrationTest(t)
defer cleanup()
ctx := context.Background()
ownerUserID, botID, scheduleID := createUserBotAndSchedule(ctx, t, queries)
defer cleanupScheduleTestData(ctx, t, queries, pool, ownerUserID, botID, scheduleID)
err := svc.Trigger(ctx, scheduleID)
if err != nil {
t.Fatalf("Trigger failed: %v", err)
}
if !mock.called {
t.Fatal("triggerer was not called")
}
if mock.botID != botID {
t.Errorf("triggerer botID = %s, want %s", mock.botID, botID)
}
if mock.payload.ID != scheduleID {
t.Errorf("payload.ID = %s, want %s", mock.payload.ID, scheduleID)
}
if mock.payload.Name != "integration-daily" {
t.Errorf("payload.Name = %s, want integration-daily", mock.payload.Name)
}
if mock.payload.Command != "run daily report" {
t.Errorf("payload.Command = %s, want run daily report", mock.payload.Command)
}
if mock.payload.OwnerUserID != ownerUserID {
t.Errorf("payload.OwnerUserID = %s, want %s", mock.payload.OwnerUserID, ownerUserID)
}
if !strings.HasPrefix(mock.token, "Bearer ") {
t.Errorf("token should have Bearer prefix, got: %s", mock.token)
}
}