mirror of
https://github.com/memohai/Memoh.git
synced 2026-04-27 07:16:19 +09:00
158 lines
4.5 KiB
Go
158 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,
|
|
Type: "personal",
|
|
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)
|
|
}
|
|
}
|