feat: authsession service

This commit is contained in:
Ilia Denisov
2026-04-08 16:23:07 +02:00
committed by GitHub
parent 28f04916af
commit 86a68ed9d0
174 changed files with 31732 additions and 112 deletions
@@ -0,0 +1,171 @@
package sendemailcode
import (
"context"
"testing"
"time"
"galaxy/authsession/internal/domain/common"
"galaxy/authsession/internal/domain/userresolution"
authtelemetry "galaxy/authsession/internal/telemetry"
"galaxy/authsession/internal/testkit"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"go.opentelemetry.io/otel/attribute"
sdkmetric "go.opentelemetry.io/otel/sdk/metric"
"go.opentelemetry.io/otel/sdk/metric/metricdata"
)
func TestExecuteRecordsSentMetric(t *testing.T) {
t.Parallel()
runtime, reader := newObservedTelemetryRuntime(t)
service, _, mailSender := newObservedSendService(t, observedSendOptions{
Telemetry: runtime,
})
_, err := service.Execute(context.Background(), Input{Email: "pilot@example.com"})
require.NoError(t, err)
require.Len(t, mailSender.RecordedInputs(), 1)
assertMetricCount(t, reader, "authsession.send_email_code.attempts", map[string]string{
"outcome": "sent",
}, 1)
}
func TestExecuteRecordsBlockedSuppressedMetric(t *testing.T) {
t.Parallel()
runtime, reader := newObservedTelemetryRuntime(t)
service, _, _ := newObservedSendService(t, observedSendOptions{
Telemetry: runtime,
SeedBlockedEmail: true,
})
_, err := service.Execute(context.Background(), Input{Email: "pilot@example.com"})
require.NoError(t, err)
assertMetricCount(t, reader, "authsession.send_email_code.attempts", map[string]string{
"outcome": "suppressed",
"reason": "blocked",
}, 1)
}
func TestExecuteRecordsThrottledMetric(t *testing.T) {
t.Parallel()
runtime, reader := newObservedTelemetryRuntime(t)
abuseProtector := &testkit.InMemorySendEmailCodeAbuseProtector{}
now := time.Unix(10, 0).UTC()
require.NoError(t, reserveSendCooldown(abuseProtector, common.Email("pilot@example.com"), now))
service, _, mailSender := newObservedSendService(t, observedSendOptions{
Telemetry: runtime,
AbuseProtector: abuseProtector,
Clock: testkit.FixedClock{Time: now},
})
_, err := service.Execute(context.Background(), Input{Email: "pilot@example.com"})
require.NoError(t, err)
assert.Empty(t, mailSender.RecordedInputs())
assertMetricCount(t, reader, "authsession.send_email_code.attempts", map[string]string{
"outcome": "throttled",
"reason": "throttled",
}, 1)
}
type observedSendOptions struct {
Telemetry *authtelemetry.Runtime
AbuseProtector *testkit.InMemorySendEmailCodeAbuseProtector
SeedBlockedEmail bool
Clock portsClock
}
type portsClock interface {
Now() time.Time
}
func newObservedSendService(t *testing.T, options observedSendOptions) (*Service, *testkit.InMemoryChallengeStore, *testkit.RecordingMailSender) {
t.Helper()
challengeStore := &testkit.InMemoryChallengeStore{}
userDirectory := &testkit.InMemoryUserDirectory{}
if options.SeedBlockedEmail {
require.NoError(t, userDirectory.SeedBlockedEmail(common.Email("pilot@example.com"), userresolution.BlockReasonCode("policy_block")))
}
mailSender := &testkit.RecordingMailSender{}
clock := options.Clock
if clock == nil {
clock = testkit.FixedClock{Time: time.Unix(10, 0).UTC()}
}
service, err := NewWithRuntime(
challengeStore,
userDirectory,
&testkit.SequenceIDGenerator{ChallengeIDs: []common.ChallengeID{"challenge-1"}},
testkit.FixedCodeGenerator{Code: "654321"},
testkit.DeterministicCodeHasher{},
mailSender,
options.AbuseProtector,
clock,
options.Telemetry,
)
require.NoError(t, err)
return service, challengeStore, mailSender
}
func newObservedTelemetryRuntime(t *testing.T) (*authtelemetry.Runtime, *sdkmetric.ManualReader) {
t.Helper()
reader := sdkmetric.NewManualReader()
provider := sdkmetric.NewMeterProvider(sdkmetric.WithReader(reader))
runtime, err := authtelemetry.New(provider)
require.NoError(t, err)
return runtime, reader
}
func assertMetricCount(t *testing.T, reader *sdkmetric.ManualReader, metricName string, wantAttrs map[string]string, wantValue int64) {
t.Helper()
var resourceMetrics metricdata.ResourceMetrics
require.NoError(t, reader.Collect(context.Background(), &resourceMetrics))
for _, scopeMetrics := range resourceMetrics.ScopeMetrics {
for _, metric := range scopeMetrics.Metrics {
if metric.Name != metricName {
continue
}
sum, ok := metric.Data.(metricdata.Sum[int64])
require.True(t, ok)
for _, point := range sum.DataPoints {
if hasMetricAttributes(point.Attributes.ToSlice(), wantAttrs) {
assert.Equal(t, wantValue, point.Value)
return
}
}
}
}
require.Failf(t, "test failed", "metric %q with attrs %v not found", metricName, wantAttrs)
}
func hasMetricAttributes(values []attribute.KeyValue, want map[string]string) bool {
if len(values) != len(want) {
return false
}
for _, value := range values {
if want[string(value.Key)] != value.Value.AsString() {
return false
}
}
return true
}