ultisuite-backend/internal/integrationtest/user.go
R3D347HR4Y fa5394e10d feat(tests): add integration testing framework and configuration
- Introduced a new `.env.test.example` file for integration test configuration.
- Added a `Makefile` to streamline test commands for unit and integration tests.
- Implemented an integration testing harness with support for PostgreSQL, MinIO, and Redis using testcontainers.
- Created a suite of integration tests covering health checks and user management functionalities.
- Enhanced CI workflow to include integration tests with necessary environment variables.
2026-06-07 19:44:29 +02:00

156 lines
4.1 KiB
Go

//go:build integration
package integrationtest
import (
"context"
"testing"
"github.com/ultisuite/ulti-backend/internal/auth"
"github.com/ultisuite/ulti-backend/internal/permission"
"github.com/ultisuite/ulti-backend/internal/users"
)
// RegularUser returns claims for a standard suite user.
func RegularUser(externalID string) *auth.Claims {
return &auth.Claims{
Sub: externalID,
Email: externalID + "@test.ultimail.local",
Name: "Test User",
Groups: []string{string(permission.RoleUser)},
}
}
// AdminUser returns claims for a platform admin.
func AdminUser(externalID string) *auth.Claims {
return &auth.Claims{
Sub: externalID,
Email: externalID + "@admin.ultimail.local",
Name: "Test Admin",
Groups: []string{string(permission.RoleAdmin), "admin:write"},
}
}
// EnsureUser provisions a user row directly (optional shortcut).
func EnsureUser(ctx context.Context, h *Harness, claims *auth.Claims) (string, error) {
return users.EnsureUser(ctx, h.Pool, claims)
}
// DisableUser marks a user as disabled in the database.
func DisableUser(ctx context.Context, h *Harness, externalID string) error {
_, err := h.Pool.Exec(ctx, `
UPDATE users SET status = 'disabled', updated_at = NOW()
WHERE external_id = $1
`, externalID)
return err
}
// RequireUserClient creates a regular user client, failing the test on error.
func RequireUserClient(t *testing.T, h *Harness) (*Client, *auth.Claims) {
t.Helper()
claims := RegularUser(NewExternalID("user"))
client, err := h.Client(claims)
if err != nil {
t.Fatalf("client: %v", err)
}
return client, claims
}
// RequireAdminClient creates an admin user client.
func RequireAdminClient(t *testing.T, h *Harness) (*Client, *auth.Claims) {
t.Helper()
claims := AdminUser(NewExternalID("admin"))
client, err := h.Client(claims)
if err != nil {
t.Fatalf("admin client: %v", err)
}
return client, claims
}
// SkipUnlessNextcloud skips the test when Nextcloud integration is not enabled.
func SkipUnlessNextcloud(t *testing.T) {
t.Helper()
env := LoadEnv()
if !env.Nextcloud || env.NextcloudURL == "" {
t.Skip("skipped: set ULTI_TEST_NEXTCLOUD=1 and ULTI_TEST_NEXTCLOUD_URL")
}
}
// SkipUnlessImmich skips when Immich tests are not enabled.
func SkipUnlessImmich(t *testing.T) {
t.Helper()
env := LoadEnv()
if !env.Immich || env.ImmichURL == "" {
t.Skip("skipped: set ULTI_TEST_IMMICH=1 and ULTI_TEST_IMMICH_URL")
}
}
// SkipUnlessJitsi skips when Jitsi tests are not enabled.
func SkipUnlessJitsi(t *testing.T) {
t.Helper()
if !LoadEnv().Jitsi {
t.Skip("skipped: set ULTI_TEST_JITSI=1")
}
}
// AssertUserProvisioned verifies the user exists after an API call.
func AssertUserProvisioned(t *testing.T, h *Harness, externalID string) {
t.Helper()
var id string
err := h.Pool.QueryRow(context.Background(), `
SELECT id FROM users WHERE external_id = $1
`, externalID).Scan(&id)
if err != nil {
t.Fatalf("user %q not provisioned: %v", externalID, err)
}
if id == "" {
t.Fatalf("empty user id for %q", externalID)
}
}
func AssertErrorCode(t *testing.T, resp *Response, wantStatus int, wantCode string) {
t.Helper()
if resp.Status != wantStatus {
t.Fatalf("status = %d, want %d; body = %s", resp.Status, wantStatus, string(resp.Body))
}
var body struct {
Code string `json:"code"`
}
if err := resp.JSON(&body); err != nil {
t.Fatalf("decode error body: %v", err)
}
if wantCode != "" && body.Code != wantCode {
t.Fatalf("error code = %q, want %q; body = %s", body.Code, wantCode, string(resp.Body))
}
}
func FailIf(err error, t *testing.T, msg string) {
t.Helper()
if err != nil {
t.Fatalf("%s: %v", msg, err)
}
}
func FailUnlessStatus(t *testing.T, resp *Response, want int) {
t.Helper()
if err := resp.MustStatus(want); err != nil {
t.Fatal(err)
}
}
// DecodeJSON decodes response body or fails the test.
func DecodeJSON(t *testing.T, resp *Response, v any) {
t.Helper()
if err := resp.JSON(v); err != nil {
t.Fatalf("decode json: %v; body = %s", err, string(resp.Body))
}
}
// FormatSub returns a readable subject for error messages.
func FormatSub(sub string) string {
if sub == "" {
return "<empty>"
}
return sub
}