You've already forked agentic-coding-workflow
test(app): integration test for login -> dispatch -> list flow
This commit is contained in:
@@ -0,0 +1,108 @@
|
|||||||
|
//go:build integration
|
||||||
|
|
||||||
|
// Package app_test 中的 integration_test.go 端到端覆盖:bootstrap admin →
|
||||||
|
// 通过 HTTP 登录拿到 token → dispatcher 写一条通知 → 用 token 通过 HTTP
|
||||||
|
// 查询未读计数为 1。所有外部依赖只有一个 postgres 容器,由 testcontainers
|
||||||
|
// 启动。需要本机 Docker;跑命令:
|
||||||
|
//
|
||||||
|
// go test -tags=integration -count=1 ./internal/app/...
|
||||||
|
package app_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"context"
|
||||||
|
"encoding/json"
|
||||||
|
"net/http"
|
||||||
|
"net/http/httptest"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/go-chi/chi/v5"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
tcpg "github.com/testcontainers/testcontainers-go/modules/postgres"
|
||||||
|
|
||||||
|
"github.com/yan1h/agent-coding-workflow/internal/audit"
|
||||||
|
"github.com/yan1h/agent-coding-workflow/internal/infra/db"
|
||||||
|
"github.com/yan1h/agent-coding-workflow/internal/infra/logger"
|
||||||
|
"github.com/yan1h/agent-coding-workflow/internal/infra/notify"
|
||||||
|
"github.com/yan1h/agent-coding-workflow/internal/transport/http/middleware"
|
||||||
|
"github.com/yan1h/agent-coding-workflow/internal/user"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestEndToEnd_LoginAndDispatchNotification(t *testing.T) {
|
||||||
|
ctx := context.Background()
|
||||||
|
|
||||||
|
container, err := tcpg.Run(ctx, "postgres:16-alpine",
|
||||||
|
tcpg.WithDatabase("acw"),
|
||||||
|
tcpg.WithUsername("acw"),
|
||||||
|
tcpg.WithPassword("acw"),
|
||||||
|
)
|
||||||
|
require.NoError(t, err)
|
||||||
|
t.Cleanup(func() { _ = container.Terminate(ctx) })
|
||||||
|
|
||||||
|
dsn, err := container.ConnectionString(ctx, "sslmode=disable")
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.NoError(t, db.Migrate(dsn, "file://../../migrations"))
|
||||||
|
|
||||||
|
pool, err := db.NewPool(ctx, dsn)
|
||||||
|
require.NoError(t, err)
|
||||||
|
t.Cleanup(pool.Close)
|
||||||
|
|
||||||
|
log := logger.New(logger.Options{Format: logger.FormatJSON, Level: logger.LevelInfo})
|
||||||
|
auditRec := audit.NewPostgresRecorder(pool)
|
||||||
|
|
||||||
|
userRepo := user.NewPostgresRepository(pool)
|
||||||
|
userSvc := user.NewService(userRepo, auditRec)
|
||||||
|
|
||||||
|
notifyRepo := notify.NewPostgresRepository(pool)
|
||||||
|
disp := notify.NewDispatcher(notifyRepo, log)
|
||||||
|
disp.Register(notify.NewDummyNotifier(log))
|
||||||
|
|
||||||
|
u, err := userSvc.Bootstrap(ctx, "admin@local", "password123")
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.NotNil(t, u)
|
||||||
|
|
||||||
|
r := chi.NewRouter()
|
||||||
|
r.Use(middleware.RequestID)
|
||||||
|
user.NewHandler(userSvc).Mount(r)
|
||||||
|
// userSvc 直接满足 middleware.SessionResolver;不需要任何函数适配器。
|
||||||
|
notify.NewHandler(notifyRepo, userSvc).Mount(r)
|
||||||
|
|
||||||
|
srv := httptest.NewServer(r)
|
||||||
|
t.Cleanup(srv.Close)
|
||||||
|
|
||||||
|
body, _ := json.Marshal(map[string]string{"email": "admin@local", "password": "password123"})
|
||||||
|
resp, err := http.Post(srv.URL+"/api/v1/auth/login", "application/json", bytes.NewReader(body))
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, http.StatusOK, resp.StatusCode)
|
||||||
|
|
||||||
|
var lr struct {
|
||||||
|
Token string `json:"token"`
|
||||||
|
}
|
||||||
|
require.NoError(t, json.NewDecoder(resp.Body).Decode(&lr))
|
||||||
|
_ = resp.Body.Close()
|
||||||
|
require.NotEmpty(t, lr.Token)
|
||||||
|
|
||||||
|
require.NoError(t, disp.Dispatch(ctx, notify.Message{
|
||||||
|
UserID: u.ID,
|
||||||
|
Topic: "test",
|
||||||
|
Severity: notify.SeverityInfo,
|
||||||
|
Title: "hello",
|
||||||
|
Body: "world",
|
||||||
|
}))
|
||||||
|
|
||||||
|
require.Eventually(t, func() bool {
|
||||||
|
req, _ := http.NewRequest("GET", srv.URL+"/api/v1/notifications/unread-count", nil)
|
||||||
|
req.Header.Set("Authorization", "Bearer "+lr.Token)
|
||||||
|
r2, err := http.DefaultClient.Do(req)
|
||||||
|
if err != nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
defer r2.Body.Close()
|
||||||
|
var out struct {
|
||||||
|
Count int `json:"count"`
|
||||||
|
}
|
||||||
|
_ = json.NewDecoder(r2.Body).Decode(&out)
|
||||||
|
return out.Count == 1
|
||||||
|
}, 2*time.Second, 50*time.Millisecond, "expected unread count to reach 1 after dispatch")
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user