Files
galaxy-game/authsession/gateway_compatibility_test.go
T
2026-04-26 20:34:39 +02:00

728 lines
24 KiB
Go

package authsession
import (
"bytes"
"context"
"crypto/ed25519"
"encoding/base64"
"encoding/json"
"fmt"
"io"
"net"
"net/http"
"strconv"
"strings"
"testing"
"time"
"galaxy/authsession/internal/adapters/mail"
"galaxy/authsession/internal/adapters/redis/challengestore"
"galaxy/authsession/internal/adapters/redis/configprovider"
"galaxy/authsession/internal/adapters/redis/projectionpublisher"
"galaxy/authsession/internal/adapters/redis/sessionstore"
"galaxy/authsession/internal/adapters/userservice"
"galaxy/authsession/internal/api/internalhttp"
"galaxy/authsession/internal/api/publichttp"
"galaxy/authsession/internal/domain/common"
"galaxy/authsession/internal/domain/devicesession"
"galaxy/authsession/internal/service/blockuser"
"galaxy/authsession/internal/service/confirmemailcode"
"galaxy/authsession/internal/service/getsession"
"galaxy/authsession/internal/service/listusersessions"
"galaxy/authsession/internal/service/revokeallusersessions"
"galaxy/authsession/internal/service/revokedevicesession"
"galaxy/authsession/internal/service/sendemailcode"
"galaxy/authsession/internal/testkit"
"github.com/alicebob/miniredis/v2"
"github.com/redis/go-redis/v9"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"go.uber.org/zap"
)
const (
gatewayCompatibilityChallengeKeyPrefix = "authsession:challenge:"
gatewayCompatibilitySessionKeyPrefix = "authsession:session:"
gatewayCompatibilityUserSessionsKeyPrefix = "authsession:user-sessions:"
gatewayCompatibilityUserActiveKeyPrefix = "authsession:user-active-sessions:"
gatewayCompatibilitySessionLimitKey = "authsession:config:active-session-limit"
gatewayCompatibilitySessionCacheKeyPrefix = "gateway:session:"
gatewayCompatibilitySessionEventsStream = "gateway:session_events"
gatewayCompatibilityStreamMaxLen int64 = 128
gatewayCompatibilityEmail = "pilot@example.com"
gatewayCompatibilityCode = "123456"
gatewayCompatibilityTimeZone = "Europe/Kaliningrad"
)
var gatewayCompatibilityClientPublicKey = mustGatewayCompatibilityClientPublicKeyBase64()
func gatewayCompatibilityConfirmRequest(challengeID string, code string, clientPublicKey string) map[string]string {
return map[string]string{
"challenge_id": challengeID,
"code": code,
"client_public_key": clientPublicKey,
"time_zone": gatewayCompatibilityTimeZone,
}
}
func TestGatewayCompatibilityConfirmReturnsGatewayReadableSessionProjection(t *testing.T) {
t.Parallel()
app := newGatewayCompatibilityHarness(t, gatewayCompatibilityOptions{})
sendResponse := gatewayCompatibilityPostJSON(t, app.publicBaseURL+"/api/v1/public/auth/send-email-code", `{"email":"pilot@example.com"}`)
assert.Equal(t, http.StatusOK, sendResponse.StatusCode)
var sendBody struct {
ChallengeID string `json:"challenge_id"`
}
require.NoError(t, json.Unmarshal([]byte(sendResponse.Body), &sendBody))
assert.Equal(t, "challenge-1", sendBody.ChallengeID)
attempts := app.mailSender.RecordedAttempts()
require.Len(t, attempts, 1)
confirmResponse := gatewayCompatibilityPostJSONValue(
t,
app.publicBaseURL+"/api/v1/public/auth/confirm-email-code",
gatewayCompatibilityConfirmRequest(sendBody.ChallengeID, attempts[0].Input.Code, gatewayCompatibilityClientPublicKey),
)
assert.Equal(t, http.StatusOK, confirmResponse.StatusCode)
var confirmBody struct {
DeviceSessionID string `json:"device_session_id"`
}
require.NoError(t, json.Unmarshal([]byte(confirmResponse.Body), &confirmBody))
assert.Equal(t, "device-session-1", confirmBody.DeviceSessionID)
record := app.mustReadGatewayCacheRecord(t, confirmBody.DeviceSessionID)
assert.Equal(t, gatewayCacheRecord{
DeviceSessionID: "device-session-1",
UserID: "user-1",
ClientPublicKey: gatewayCompatibilityClientPublicKey,
Status: "active",
}, record)
events := app.mustReadGatewaySessionEvents(t, confirmBody.DeviceSessionID)
require.NotEmpty(t, events)
assert.Equal(t, gatewaySessionEventRecord{
DeviceSessionID: "device-session-1",
UserID: "user-1",
ClientPublicKey: gatewayCompatibilityClientPublicKey,
Status: "active",
}, events[len(events)-1])
}
func TestGatewayCompatibilityRevokePublishesRevokedGatewayProjection(t *testing.T) {
t.Parallel()
app := newGatewayCompatibilityHarness(t, gatewayCompatibilityOptions{})
sessionID := app.createSessionThroughPublicFlow(t)
revokeResponse := gatewayCompatibilityPostJSON(
t,
app.internalBaseURL+"/api/v1/internal/sessions/"+sessionID+"/revoke",
`{"reason_code":"admin_revoke","actor":{"type":"system"}}`,
)
assert.Equal(t, http.StatusOK, revokeResponse.StatusCode)
assert.JSONEq(t, `{"outcome":"revoked","device_session_id":"`+sessionID+`","affected_session_count":1}`, revokeResponse.Body)
record := app.mustReadGatewayCacheRecord(t, sessionID)
require.NotNil(t, record.RevokedAtMS)
assert.Equal(t, gatewayCacheRecord{
DeviceSessionID: sessionID,
UserID: "user-1",
ClientPublicKey: gatewayCompatibilityClientPublicKey,
Status: "revoked",
RevokedAtMS: int64Pointer(app.now.UnixMilli()),
}, record)
events := app.mustReadGatewaySessionEvents(t, sessionID)
require.NotEmpty(t, events)
last := events[len(events)-1]
require.NotNil(t, last.RevokedAtMS)
assert.Equal(t, gatewaySessionEventRecord{
DeviceSessionID: sessionID,
UserID: "user-1",
ClientPublicKey: gatewayCompatibilityClientPublicKey,
Status: "revoked",
RevokedAtMS: int64Pointer(app.now.UnixMilli()),
}, last)
}
func TestGatewayCompatibilityRepeatedConfirmReturnsSameSessionID(t *testing.T) {
t.Parallel()
app := newGatewayCompatibilityHarness(t, gatewayCompatibilityOptions{})
sendResponse := gatewayCompatibilityPostJSON(t, app.publicBaseURL+"/api/v1/public/auth/send-email-code", `{"email":"pilot@example.com"}`)
assert.Equal(t, http.StatusOK, sendResponse.StatusCode)
var sendBody struct {
ChallengeID string `json:"challenge_id"`
}
require.NoError(t, json.Unmarshal([]byte(sendResponse.Body), &sendBody))
attempts := app.mailSender.RecordedAttempts()
require.Len(t, attempts, 1)
requestBody := gatewayCompatibilityConfirmRequest(sendBody.ChallengeID, attempts[0].Input.Code, gatewayCompatibilityClientPublicKey)
first := gatewayCompatibilityPostJSONValue(t, app.publicBaseURL+"/api/v1/public/auth/confirm-email-code", requestBody)
second := gatewayCompatibilityPostJSONValue(t, app.publicBaseURL+"/api/v1/public/auth/confirm-email-code", requestBody)
assert.Equal(t, http.StatusOK, first.StatusCode)
assert.Equal(t, http.StatusOK, second.StatusCode)
var firstBody struct {
DeviceSessionID string `json:"device_session_id"`
}
var secondBody struct {
DeviceSessionID string `json:"device_session_id"`
}
require.NoError(t, json.Unmarshal([]byte(first.Body), &firstBody))
require.NoError(t, json.Unmarshal([]byte(second.Body), &secondBody))
assert.Equal(t, firstBody.DeviceSessionID, secondBody.DeviceSessionID)
record := app.mustReadGatewayCacheRecord(t, firstBody.DeviceSessionID)
assert.Equal(t, gatewayCacheRecord{
DeviceSessionID: firstBody.DeviceSessionID,
UserID: "user-1",
ClientPublicKey: gatewayCompatibilityClientPublicKey,
Status: "active",
}, record)
}
func TestGatewayCompatibilityBlockedEmailSendRemainsSuccessShaped(t *testing.T) {
t.Parallel()
app := newGatewayCompatibilityHarness(t, gatewayCompatibilityOptions{
SeedBlockedEmail: true,
})
response := gatewayCompatibilityPostJSON(t, app.publicBaseURL+"/api/v1/public/auth/send-email-code", `{"email":"pilot@example.com"}`)
assert.Equal(t, http.StatusOK, response.StatusCode)
var body map[string]string
require.NoError(t, json.Unmarshal([]byte(response.Body), &body))
assert.Equal(t, map[string]string{"challenge_id": "challenge-1"}, body)
}
func TestGatewayCompatibilitySessionLimitExceededReturnsStableClientError(t *testing.T) {
t.Parallel()
limit := 1
app := newGatewayCompatibilityHarness(t, gatewayCompatibilityOptions{
SeedExistingUser: true,
SessionLimit: &limit,
SeedActiveSessions: []devicesession.Session{
gatewayCompatibilityActiveSession(
t,
"device-session-existing",
"user-1",
gatewayCompatibilityClientPublicKey,
time.Date(2026, 4, 5, 11, 58, 0, 0, time.UTC),
),
},
})
sendResponse := gatewayCompatibilityPostJSON(t, app.publicBaseURL+"/api/v1/public/auth/send-email-code", `{"email":"pilot@example.com"}`)
assert.Equal(t, http.StatusOK, sendResponse.StatusCode)
attempts := app.mailSender.RecordedAttempts()
require.Len(t, attempts, 1)
confirmResponse := gatewayCompatibilityPostJSONValue(
t,
app.publicBaseURL+"/api/v1/public/auth/confirm-email-code",
gatewayCompatibilityConfirmRequest("challenge-1", attempts[0].Input.Code, gatewayCompatibilityClientPublicKey),
)
assert.Equal(t, http.StatusConflict, confirmResponse.StatusCode)
assert.JSONEq(t, `{"error":{"code":"session_limit_exceeded","message":"active session limit would be exceeded"}}`, confirmResponse.Body)
}
func TestGatewayCompatibilityMalformedClientPublicKeyReturnsStableError(t *testing.T) {
t.Parallel()
app := newGatewayCompatibilityHarness(t, gatewayCompatibilityOptions{})
response := gatewayCompatibilityPostJSON(
t,
app.publicBaseURL+"/api/v1/public/auth/confirm-email-code",
`{"challenge_id":"challenge-123","code":"123456","client_public_key":"invalid","time_zone":"`+gatewayCompatibilityTimeZone+`"}`,
)
assert.Equal(t, http.StatusBadRequest, response.StatusCode)
assert.JSONEq(t, `{"error":{"code":"invalid_client_public_key","message":"client_public_key is not a valid base64-encoded raw 32-byte Ed25519 public key"}}`, response.Body)
}
type gatewayCompatibilityOptions struct {
SeedBlockedEmail bool
SeedExistingUser bool
SessionLimit *int
SeedActiveSessions []devicesession.Session
}
// gatewayCompatibilityHarness owns one gateway-focused integration test setup
// with real HTTP servers and real Redis-backed authsession adapters.
type gatewayCompatibilityHarness struct {
publicBaseURL string
internalBaseURL string
mailSender *mail.StubSender
redisClient *redis.Client
now time.Time
}
func newGatewayCompatibilityHarness(t *testing.T, options gatewayCompatibilityOptions) gatewayCompatibilityHarness {
t.Helper()
now := time.Date(2026, 4, 5, 12, 0, 0, 0, time.UTC)
redisServer := miniredis.RunT(t)
redisClient := redis.NewClient(&redis.Options{
Addr: redisServer.Addr(),
Protocol: 2,
DisableIdentity: true,
})
t.Cleanup(func() {
assert.NoError(t, redisClient.Close())
})
if options.SessionLimit != nil {
redisServer.Set(gatewayCompatibilitySessionLimitKey, strconv.Itoa(*options.SessionLimit))
}
challengeStore, err := challengestore.New(redisClient, challengestore.Config{
KeyPrefix: gatewayCompatibilityChallengeKeyPrefix,
OperationTimeout: 250 * time.Millisecond,
})
require.NoError(t, err)
sessionStore, err := sessionstore.New(redisClient, sessionstore.Config{
SessionKeyPrefix: gatewayCompatibilitySessionKeyPrefix,
UserSessionsKeyPrefix: gatewayCompatibilityUserSessionsKeyPrefix,
UserActiveSessionsKeyPrefix: gatewayCompatibilityUserActiveKeyPrefix,
OperationTimeout: 250 * time.Millisecond,
})
require.NoError(t, err)
configStore, err := configprovider.New(redisClient, configprovider.Config{
SessionLimitKey: gatewayCompatibilitySessionLimitKey,
OperationTimeout: 250 * time.Millisecond,
})
require.NoError(t, err)
publisher, err := projectionpublisher.New(redisClient, projectionpublisher.Config{
SessionCacheKeyPrefix: gatewayCompatibilitySessionCacheKeyPrefix,
SessionEventsStream: gatewayCompatibilitySessionEventsStream,
StreamMaxLen: gatewayCompatibilityStreamMaxLen,
OperationTimeout: 250 * time.Millisecond,
})
require.NoError(t, err)
userDirectory := &userservice.StubDirectory{}
if options.SeedBlockedEmail {
require.NoError(t, userDirectory.SeedBlockedEmail(common.Email(gatewayCompatibilityEmail), "policy_blocked"))
}
if options.SeedExistingUser {
require.NoError(t, userDirectory.SeedExisting(common.Email(gatewayCompatibilityEmail), common.UserID("user-1")))
}
for _, session := range options.SeedActiveSessions {
require.NoError(t, sessionStore.Create(context.Background(), session))
}
mailSender := &mail.StubSender{}
idGenerator := &testkit.SequenceIDGenerator{}
codeGenerator := testkit.FixedCodeGenerator{Code: gatewayCompatibilityCode}
codeHasher := testkit.DeterministicCodeHasher{}
clock := testkit.FixedClock{Time: now}
sendEmailCodeService, err := sendemailcode.NewWithObservability(
challengeStore,
userDirectory,
idGenerator,
codeGenerator,
codeHasher,
mailSender,
nil,
clock,
zap.NewNop(),
nil,
)
require.NoError(t, err)
confirmEmailCodeService, err := confirmemailcode.NewWithObservability(
challengeStore,
sessionStore,
userDirectory,
configStore,
publisher,
idGenerator,
codeHasher,
clock,
zap.NewNop(),
nil,
)
require.NoError(t, err)
getSessionService, err := getsession.New(sessionStore)
require.NoError(t, err)
listUserSessionsService, err := listusersessions.New(sessionStore)
require.NoError(t, err)
revokeDeviceSessionService, err := revokedevicesession.NewWithObservability(sessionStore, publisher, clock, zap.NewNop(), nil)
require.NoError(t, err)
revokeAllUserSessionsService, err := revokeallusersessions.NewWithObservability(sessionStore, userDirectory, publisher, clock, zap.NewNop(), nil)
require.NoError(t, err)
blockUserService, err := blockuser.NewWithObservability(userDirectory, sessionStore, publisher, clock, zap.NewNop(), nil)
require.NoError(t, err)
publicCfg := publichttp.DefaultConfig()
publicCfg.Addr = gatewayCompatibilityFreeAddr(t)
publicServer, err := publichttp.NewServer(publicCfg, publichttp.Dependencies{
SendEmailCode: sendEmailCodeService,
ConfirmEmailCode: confirmEmailCodeService,
Logger: zap.NewNop(),
})
require.NoError(t, err)
internalCfg := internalhttp.DefaultConfig()
internalCfg.Addr = gatewayCompatibilityFreeAddr(t)
internalServer, err := internalhttp.NewServer(internalCfg, internalhttp.Dependencies{
GetSession: getSessionService,
ListUserSessions: listUserSessionsService,
RevokeDeviceSession: revokeDeviceSessionService,
RevokeAllUserSessions: revokeAllUserSessionsService,
BlockUser: blockUserService,
Logger: zap.NewNop(),
})
require.NoError(t, err)
gatewayCompatibilityRunServer(t, publicServer.Run, publicServer.Shutdown, publicCfg.Addr)
gatewayCompatibilityRunServer(t, internalServer.Run, internalServer.Shutdown, internalCfg.Addr)
return gatewayCompatibilityHarness{
publicBaseURL: "http://" + publicCfg.Addr,
internalBaseURL: "http://" + internalCfg.Addr,
mailSender: mailSender,
redisClient: redisClient,
now: now,
}
}
func (h gatewayCompatibilityHarness) createSessionThroughPublicFlow(t *testing.T) string {
t.Helper()
sendResponse := gatewayCompatibilityPostJSON(t, h.publicBaseURL+"/api/v1/public/auth/send-email-code", `{"email":"pilot@example.com"}`)
assert.Equal(t, http.StatusOK, sendResponse.StatusCode)
var sendBody struct {
ChallengeID string `json:"challenge_id"`
}
require.NoError(t, json.Unmarshal([]byte(sendResponse.Body), &sendBody))
attempts := h.mailSender.RecordedAttempts()
require.Len(t, attempts, 1)
confirmResponse := gatewayCompatibilityPostJSONValue(
t,
h.publicBaseURL+"/api/v1/public/auth/confirm-email-code",
gatewayCompatibilityConfirmRequest(sendBody.ChallengeID, attempts[0].Input.Code, gatewayCompatibilityClientPublicKey),
)
assert.Equal(t, http.StatusOK, confirmResponse.StatusCode)
var confirmBody struct {
DeviceSessionID string `json:"device_session_id"`
}
require.NoError(t, json.Unmarshal([]byte(confirmResponse.Body), &confirmBody))
return confirmBody.DeviceSessionID
}
// gatewayCacheRecord mirrors the strict gateway Redis session-cache wire
// contract used on the authenticated hot path.
type gatewayCacheRecord struct {
DeviceSessionID string `json:"device_session_id"`
UserID string `json:"user_id"`
ClientPublicKey string `json:"client_public_key"`
Status string `json:"status"`
RevokedAtMS *int64 `json:"revoked_at_ms,omitempty"`
}
func (h gatewayCompatibilityHarness) mustReadGatewayCacheRecord(t *testing.T, deviceSessionID string) gatewayCacheRecord {
t.Helper()
payload, err := h.redisClient.Get(context.Background(), gatewayCompatibilitySessionCacheKeyPrefix+deviceSessionID).Bytes()
require.NoError(t, err)
decoder := json.NewDecoder(bytes.NewReader(payload))
decoder.DisallowUnknownFields()
var record gatewayCacheRecord
require.NoError(t, decoder.Decode(&record))
err = decoder.Decode(&struct{}{})
require.ErrorIs(t, err, io.EOF)
require.NotEmpty(t, record.DeviceSessionID)
require.Equal(t, deviceSessionID, record.DeviceSessionID)
require.NotEmpty(t, record.UserID)
require.NotEmpty(t, record.ClientPublicKey)
require.Contains(t, []string{"active", "revoked"}, record.Status)
return record
}
// gatewaySessionEventRecord mirrors the strict gateway Redis Stream event
// contract for full session snapshots.
type gatewaySessionEventRecord struct {
DeviceSessionID string
UserID string
ClientPublicKey string
Status string
RevokedAtMS *int64
}
func (h gatewayCompatibilityHarness) mustReadGatewaySessionEvents(t *testing.T, deviceSessionID string) []gatewaySessionEventRecord {
t.Helper()
entries, err := h.redisClient.XRange(context.Background(), gatewayCompatibilitySessionEventsStream, "-", "+").Result()
require.NoError(t, err)
records := make([]gatewaySessionEventRecord, 0, len(entries))
for _, entry := range entries {
record := decodeGatewaySessionEvent(t, entry.Values)
if record.DeviceSessionID == deviceSessionID {
records = append(records, record)
}
}
require.NotEmpty(t, records)
return records
}
func decodeGatewaySessionEvent(t *testing.T, values map[string]any) gatewaySessionEventRecord {
t.Helper()
requiredKeys := map[string]struct{}{
"device_session_id": {},
"user_id": {},
"client_public_key": {},
"status": {},
}
optionalKeys := map[string]struct{}{
"revoked_at_ms": {},
}
for key := range values {
if _, ok := requiredKeys[key]; ok {
continue
}
if _, ok := optionalKeys[key]; ok {
continue
}
require.Failf(t, "test failed", "decode gateway session event: unsupported field %q", key)
}
record := gatewaySessionEventRecord{
DeviceSessionID: gatewayCompatibilityRequiredStringField(t, values, "device_session_id"),
UserID: gatewayCompatibilityRequiredStringField(t, values, "user_id"),
ClientPublicKey: gatewayCompatibilityRequiredStringField(t, values, "client_public_key"),
Status: gatewayCompatibilityRequiredStringField(t, values, "status"),
}
require.Contains(t, []string{"active", "revoked"}, record.Status)
if rawRevokedAtMS, ok := values["revoked_at_ms"]; ok {
parsed := gatewayCompatibilityParseInt64Field(t, rawRevokedAtMS, "revoked_at_ms")
record.RevokedAtMS = &parsed
}
return record
}
func gatewayCompatibilityRequiredStringField(t *testing.T, values map[string]any, field string) string {
t.Helper()
value, ok := values[field]
require.Truef(t, ok, "decode gateway session event: missing %s", field)
stringValue := gatewayCompatibilityCoerceString(t, value, field)
require.NotEmptyf(t, strings.TrimSpace(stringValue), "decode gateway session event: %s must not be empty", field)
return stringValue
}
func gatewayCompatibilityParseInt64Field(t *testing.T, value any, field string) int64 {
t.Helper()
stringValue := gatewayCompatibilityCoerceString(t, value, field)
parsed, err := strconv.ParseInt(strings.TrimSpace(stringValue), 10, 64)
require.NoErrorf(t, err, "decode gateway session event: %s", field)
return parsed
}
func gatewayCompatibilityCoerceString(t *testing.T, value any, field string) string {
t.Helper()
switch typed := value.(type) {
case string:
return typed
case []byte:
return string(typed)
case fmt.Stringer:
return typed.String()
case int:
return strconv.Itoa(typed)
case int64:
return strconv.FormatInt(typed, 10)
case uint64:
return strconv.FormatUint(typed, 10)
default:
require.Failf(t, "test failed", "decode gateway session event: %s: unsupported value type %T", field, value)
return ""
}
}
func gatewayCompatibilityRunServer(
t *testing.T,
run func(context.Context) error,
shutdown func(context.Context) error,
addr string,
) {
t.Helper()
errCh := make(chan error, 1)
go func() {
errCh <- run(context.Background())
}()
gatewayCompatibilityWaitForTCP(t, addr)
t.Cleanup(func() {
shutdownCtx, cancel := context.WithTimeout(context.Background(), 2*time.Second)
defer cancel()
assert.NoError(t, shutdown(shutdownCtx))
assert.NoError(t, <-errCh)
})
}
func gatewayCompatibilityWaitForTCP(t *testing.T, addr string) {
t.Helper()
require.Eventually(t, func() bool {
conn, err := net.DialTimeout("tcp", addr, 50*time.Millisecond)
if err != nil {
return false
}
_ = conn.Close()
return true
}, 5*time.Second, 25*time.Millisecond)
}
func gatewayCompatibilityFreeAddr(t *testing.T) string {
t.Helper()
listener, err := net.Listen("tcp", "127.0.0.1:0")
require.NoError(t, err)
defer func() {
assert.NoError(t, listener.Close())
}()
return listener.Addr().String()
}
type gatewayCompatibilityHTTPResponse struct {
StatusCode int
Body string
}
func gatewayCompatibilityPostJSON(t *testing.T, url string, body string) gatewayCompatibilityHTTPResponse {
t.Helper()
return gatewayCompatibilityPostJSONWithHeaders(t, url, body, nil)
}
func gatewayCompatibilityPostJSONWithHeaders(t *testing.T, url string, body string, headers map[string]string) gatewayCompatibilityHTTPResponse {
t.Helper()
request, err := http.NewRequest(http.MethodPost, url, bytes.NewBufferString(body))
require.NoError(t, err)
request.Header.Set("Content-Type", "application/json")
for key, value := range headers {
if strings.TrimSpace(value) == "" {
continue
}
request.Header.Set(key, value)
}
response, err := http.DefaultClient.Do(request)
require.NoError(t, err)
defer response.Body.Close()
payload, err := io.ReadAll(response.Body)
require.NoError(t, err)
return gatewayCompatibilityHTTPResponse{
StatusCode: response.StatusCode,
Body: string(payload),
}
}
func gatewayCompatibilityPostJSONValue(t *testing.T, url string, value any) gatewayCompatibilityHTTPResponse {
t.Helper()
payload, err := json.Marshal(value)
require.NoError(t, err)
return gatewayCompatibilityPostJSON(t, url, string(payload))
}
func gatewayCompatibilityPostJSONValueWithHeaders(t *testing.T, url string, value any, headers map[string]string) gatewayCompatibilityHTTPResponse {
t.Helper()
payload, err := json.Marshal(value)
require.NoError(t, err)
return gatewayCompatibilityPostJSONWithHeaders(t, url, string(payload), headers)
}
func gatewayCompatibilityActiveSession(
t *testing.T,
deviceSessionID string,
userID string,
clientPublicKeyBase64 string,
createdAt time.Time,
) devicesession.Session {
t.Helper()
keyBytes, err := base64.StdEncoding.DecodeString(clientPublicKeyBase64)
require.NoError(t, err)
clientPublicKey, err := common.NewClientPublicKey(ed25519.PublicKey(keyBytes))
require.NoError(t, err)
session := devicesession.Session{
ID: common.DeviceSessionID(deviceSessionID),
UserID: common.UserID(userID),
ClientPublicKey: clientPublicKey,
Status: devicesession.StatusActive,
CreatedAt: createdAt,
}
require.NoError(t, session.Validate())
return session
}
func mustGatewayCompatibilityClientPublicKeyBase64() string {
key := make([]byte, ed25519.PublicKeySize)
for index := range key {
key[index] = byte(index + 1)
}
return base64.StdEncoding.EncodeToString(key)
}
func int64Pointer(value int64) *int64 {
return &value
}