274 lines
7.3 KiB
Go
274 lines
7.3 KiB
Go
package authsession
|
|
|
|
import (
|
|
"bytes"
|
|
"context"
|
|
"fmt"
|
|
"io"
|
|
"net"
|
|
"os"
|
|
"os/exec"
|
|
"path/filepath"
|
|
"runtime"
|
|
"strings"
|
|
"syscall"
|
|
"testing"
|
|
"time"
|
|
|
|
"galaxy/authsession/internal/adapters/userservice"
|
|
"galaxy/authsession/internal/domain/common"
|
|
"galaxy/authsession/internal/domain/userresolution"
|
|
"galaxy/authsession/internal/ports"
|
|
|
|
"github.com/alicebob/miniredis/v2"
|
|
)
|
|
|
|
func TestUserServiceRESTClientWorksAgainstRealUserServiceRuntime(t *testing.T) {
|
|
redisServer := miniredis.RunT(t)
|
|
internalAddr := freeTCPAddress(t)
|
|
binaryPath := buildUserServiceBinary(t)
|
|
process := startUserServiceProcess(t, binaryPath, map[string]string{
|
|
"USERSERVICE_INTERNAL_HTTP_ADDR": internalAddr,
|
|
"USERSERVICE_REDIS_ADDR": redisServer.Addr(),
|
|
})
|
|
waitForTCP(t, process, internalAddr)
|
|
|
|
client, err := userservice.NewRESTClient(userservice.Config{
|
|
BaseURL: "http://" + internalAddr,
|
|
RequestTimeout: 500 * time.Millisecond,
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("NewRESTClient() error = %v, want nil", err)
|
|
}
|
|
t.Cleanup(func() {
|
|
_ = client.Close()
|
|
})
|
|
|
|
creatableEmail := common.Email("pilot@example.com")
|
|
|
|
resolution, err := client.ResolveByEmail(context.Background(), creatableEmail)
|
|
if err != nil {
|
|
t.Fatalf("ResolveByEmail(creatable) error = %v, want nil", err)
|
|
}
|
|
if got, want := resolution.Kind, userresolution.KindCreatable; got != want {
|
|
t.Fatalf("ResolveByEmail(creatable).Kind = %q, want %q", got, want)
|
|
}
|
|
|
|
created, err := client.EnsureUserByEmail(context.Background(), ports.EnsureUserInput{
|
|
Email: creatableEmail,
|
|
RegistrationContext: &ports.RegistrationContext{
|
|
PreferredLanguage: "en",
|
|
TimeZone: "Europe/Kaliningrad",
|
|
},
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("EnsureUserByEmail(created) error = %v, want nil", err)
|
|
}
|
|
if got, want := created.Outcome, ports.EnsureUserOutcomeCreated; got != want {
|
|
t.Fatalf("EnsureUserByEmail(created).Outcome = %q, want %q", got, want)
|
|
}
|
|
if created.UserID.IsZero() {
|
|
t.Fatalf("EnsureUserByEmail(created).UserID = zero, want non-zero")
|
|
}
|
|
|
|
existing, err := client.ResolveByEmail(context.Background(), creatableEmail)
|
|
if err != nil {
|
|
t.Fatalf("ResolveByEmail(existing) error = %v, want nil", err)
|
|
}
|
|
if got, want := existing.Kind, userresolution.KindExisting; got != want {
|
|
t.Fatalf("ResolveByEmail(existing).Kind = %q, want %q", got, want)
|
|
}
|
|
if got, want := existing.UserID, created.UserID; got != want {
|
|
t.Fatalf("ResolveByEmail(existing).UserID = %q, want %q", got, want)
|
|
}
|
|
|
|
exists, err := client.ExistsByUserID(context.Background(), created.UserID)
|
|
if err != nil {
|
|
t.Fatalf("ExistsByUserID(existing) error = %v, want nil", err)
|
|
}
|
|
if !exists {
|
|
t.Fatalf("ExistsByUserID(existing) = false, want true")
|
|
}
|
|
|
|
blocked, err := client.BlockByUserID(context.Background(), ports.BlockUserByIDInput{
|
|
UserID: created.UserID,
|
|
ReasonCode: userresolution.BlockReasonCode("policy_blocked"),
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("BlockByUserID() error = %v, want nil", err)
|
|
}
|
|
if got, want := blocked.Outcome, ports.BlockUserOutcomeBlocked; got != want {
|
|
t.Fatalf("BlockByUserID().Outcome = %q, want %q", got, want)
|
|
}
|
|
if got, want := blocked.UserID, created.UserID; got != want {
|
|
t.Fatalf("BlockByUserID().UserID = %q, want %q", got, want)
|
|
}
|
|
|
|
repeated, err := client.BlockByEmail(context.Background(), ports.BlockUserByEmailInput{
|
|
Email: creatableEmail,
|
|
ReasonCode: userresolution.BlockReasonCode("policy_blocked"),
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("BlockByEmail(repeated) error = %v, want nil", err)
|
|
}
|
|
if got, want := repeated.Outcome, ports.BlockUserOutcomeAlreadyBlocked; got != want {
|
|
t.Fatalf("BlockByEmail(repeated).Outcome = %q, want %q", got, want)
|
|
}
|
|
if got, want := repeated.UserID, created.UserID; got != want {
|
|
t.Fatalf("BlockByEmail(repeated).UserID = %q, want %q", got, want)
|
|
}
|
|
|
|
blockedResolution, err := client.ResolveByEmail(context.Background(), creatableEmail)
|
|
if err != nil {
|
|
t.Fatalf("ResolveByEmail(blocked) error = %v, want nil", err)
|
|
}
|
|
if got, want := blockedResolution.Kind, userresolution.KindBlocked; got != want {
|
|
t.Fatalf("ResolveByEmail(blocked).Kind = %q, want %q", got, want)
|
|
}
|
|
if got, want := blockedResolution.BlockReasonCode, userresolution.BlockReasonCode("policy_blocked"); got != want {
|
|
t.Fatalf("ResolveByEmail(blocked).BlockReasonCode = %q, want %q", got, want)
|
|
}
|
|
}
|
|
|
|
type userServiceProcess struct {
|
|
cmd *exec.Cmd
|
|
doneCh chan struct{}
|
|
logs bytes.Buffer
|
|
}
|
|
|
|
func startUserServiceProcess(t *testing.T, binaryPath string, env map[string]string) *userServiceProcess {
|
|
t.Helper()
|
|
|
|
cmd := exec.Command(binaryPath)
|
|
cmd.Env = mergeEnvironment(os.Environ(), env)
|
|
|
|
process := &userServiceProcess{
|
|
cmd: cmd,
|
|
doneCh: make(chan struct{}),
|
|
}
|
|
cmd.Stdout = &process.logs
|
|
cmd.Stderr = &process.logs
|
|
|
|
if err := cmd.Start(); err != nil {
|
|
t.Fatalf("start user service process: %v", err)
|
|
}
|
|
|
|
go func() {
|
|
_ = cmd.Wait()
|
|
close(process.doneCh)
|
|
}()
|
|
|
|
t.Cleanup(func() {
|
|
stopUserServiceProcess(t, process)
|
|
if t.Failed() {
|
|
t.Logf("userservice logs:\n%s", process.logs.String())
|
|
}
|
|
})
|
|
|
|
return process
|
|
}
|
|
|
|
func stopUserServiceProcess(t *testing.T, process *userServiceProcess) {
|
|
t.Helper()
|
|
|
|
if process == nil || process.cmd == nil || process.cmd.Process == nil {
|
|
return
|
|
}
|
|
|
|
select {
|
|
case <-process.doneCh:
|
|
return
|
|
default:
|
|
}
|
|
|
|
_ = process.cmd.Process.Signal(syscall.SIGTERM)
|
|
|
|
select {
|
|
case <-process.doneCh:
|
|
case <-time.After(5 * time.Second):
|
|
_ = process.cmd.Process.Kill()
|
|
<-process.doneCh
|
|
}
|
|
}
|
|
|
|
func waitForTCP(t *testing.T, process *userServiceProcess, address string) {
|
|
t.Helper()
|
|
|
|
deadline := time.Now().Add(10 * time.Second)
|
|
for time.Now().Before(deadline) {
|
|
select {
|
|
case <-process.doneCh:
|
|
t.Fatalf("userservice exited before %s became reachable\n%s", address, process.logs.String())
|
|
default:
|
|
}
|
|
|
|
conn, err := net.DialTimeout("tcp", address, 100*time.Millisecond)
|
|
if err == nil {
|
|
_ = conn.Close()
|
|
return
|
|
}
|
|
|
|
time.Sleep(25 * time.Millisecond)
|
|
}
|
|
|
|
t.Fatalf("userservice did not become reachable at %s\n%s", address, process.logs.String())
|
|
}
|
|
|
|
func freeTCPAddress(t *testing.T) string {
|
|
t.Helper()
|
|
|
|
listener, err := net.Listen("tcp", "127.0.0.1:0")
|
|
if err != nil {
|
|
t.Fatalf("reserve free TCP address: %v", err)
|
|
}
|
|
defer listener.Close()
|
|
|
|
return listener.Addr().String()
|
|
}
|
|
|
|
func buildUserServiceBinary(t *testing.T) string {
|
|
t.Helper()
|
|
|
|
outputPath := filepath.Join(t.TempDir(), "userservice")
|
|
cmd := exec.Command("go", "build", "-o", outputPath, "./user/cmd/userservice")
|
|
cmd.Dir = repositoryRoot(t)
|
|
output, err := cmd.CombinedOutput()
|
|
if err != nil {
|
|
t.Fatalf("build userservice binary: %v\n%s", err, output)
|
|
}
|
|
|
|
return outputPath
|
|
}
|
|
|
|
func repositoryRoot(t *testing.T) string {
|
|
t.Helper()
|
|
|
|
_, file, _, ok := runtime.Caller(0)
|
|
if !ok {
|
|
t.Fatal("resolve repository root: runtime caller unavailable")
|
|
}
|
|
|
|
return filepath.Clean(filepath.Join(filepath.Dir(file), ".."))
|
|
}
|
|
|
|
func mergeEnvironment(base []string, overrides map[string]string) []string {
|
|
values := make(map[string]string, len(base)+len(overrides))
|
|
for _, entry := range base {
|
|
name, value, ok := strings.Cut(entry, "=")
|
|
if ok {
|
|
values[name] = value
|
|
}
|
|
}
|
|
for name, value := range overrides {
|
|
values[name] = value
|
|
}
|
|
|
|
merged := make([]string, 0, len(values))
|
|
for name, value := range values {
|
|
merged = append(merged, fmt.Sprintf("%s=%s", name, value))
|
|
}
|
|
return merged
|
|
}
|
|
|
|
var _ io.Writer = (*bytes.Buffer)(nil)
|