package userservice import ( "context" "encoding/json" "io" "net/http" "net/http/httptest" "testing" "time" "galaxy/notification/internal/service/acceptintent" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) func TestNewClient(t *testing.T) { t.Parallel() tests := []struct { name string cfg Config wantErr string }{ { name: "valid config", cfg: Config{ BaseURL: "http://127.0.0.1:8080", RequestTimeout: time.Second, }, }, { name: "empty base url", cfg: Config{ RequestTimeout: time.Second, }, wantErr: "base URL must not be empty", }, { name: "relative base url", cfg: Config{ BaseURL: "/relative", RequestTimeout: time.Second, }, wantErr: "base URL must be absolute", }, { name: "non positive timeout", cfg: Config{ BaseURL: "http://127.0.0.1:8080", }, wantErr: "request timeout must be positive", }, } for _, tt := range tests { tt := tt t.Run(tt.name, func(t *testing.T) { t.Parallel() client, err := NewClient(tt.cfg) if tt.wantErr != "" { require.Error(t, err) assert.ErrorContains(t, err, tt.wantErr) return } require.NoError(t, err) assert.NoError(t, client.Close()) }) } } func TestClientGetUserByID(t *testing.T) { t.Parallel() t.Run("success", func(t *testing.T) { t.Parallel() var captured capturedRequest server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { captured = captureRequest(t, r) writeJSON(t, w, http.StatusOK, map[string]any{ "user": map[string]any{ "user_id": "user-123", "email": "pilot@example.com", "preferred_language": "en-US", "time_zone": "Europe/Kaliningrad", }, }) })) defer server.Close() client := newTestClient(t, server.URL, 250*time.Millisecond) record, err := client.GetUserByID(context.Background(), "user-123") require.NoError(t, err) require.Equal(t, acceptintent.UserRecord{ Email: "pilot@example.com", PreferredLanguage: "en-US", }, record) require.Equal(t, capturedRequest{ Method: http.MethodGet, Path: "/api/v1/internal/users/user-123", }, captured) }) t.Run("subject not found", func(t *testing.T) { t.Parallel() server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { writeJSON(t, w, http.StatusNotFound, map[string]any{ "error": map[string]any{ "code": "subject_not_found", "message": "subject not found", }, }) })) defer server.Close() client := newTestClient(t, server.URL, 250*time.Millisecond) _, err := client.GetUserByID(context.Background(), "user-missing") require.Error(t, err) require.ErrorIs(t, err, acceptintent.ErrRecipientNotFound) }) t.Run("invalid email is treated as dependency failure", func(t *testing.T) { t.Parallel() server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { writeJSON(t, w, http.StatusOK, map[string]any{ "user": map[string]any{ "email": "bad@@example.com", "preferred_language": "en", }, }) })) defer server.Close() client := newTestClient(t, server.URL, 250*time.Millisecond) _, err := client.GetUserByID(context.Background(), "user-123") require.Error(t, err) require.NotErrorIs(t, err, acceptintent.ErrRecipientNotFound) require.ErrorContains(t, err, "invalid success response") }) t.Run("timeout", func(t *testing.T) { t.Parallel() server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { <-r.Context().Done() })) defer server.Close() client := newTestClient(t, server.URL, 10*time.Millisecond) _, err := client.GetUserByID(context.Background(), "user-123") require.Error(t, err) require.NotErrorIs(t, err, acceptintent.ErrRecipientNotFound) require.ErrorContains(t, err, "context deadline exceeded") }) } type capturedRequest struct { Method string Path string } func newTestClient(t *testing.T, baseURL string, requestTimeout time.Duration) *Client { t.Helper() client, err := newClient( Config{ BaseURL: baseURL, RequestTimeout: requestTimeout, }, &http.Client{Transport: http.DefaultTransport.(*http.Transport).Clone()}, func() {}, ) require.NoError(t, err) return client } func captureRequest(t *testing.T, request *http.Request) capturedRequest { t.Helper() _, err := io.ReadAll(request.Body) require.NoError(t, err) require.NoError(t, request.Body.Close()) return capturedRequest{ Method: request.Method, Path: request.URL.Path, } } func writeJSON(t *testing.T, writer http.ResponseWriter, statusCode int, payload any) { t.Helper() body, err := json.Marshal(payload) require.NoError(t, err) writer.Header().Set("Content-Type", "application/json") writer.WriteHeader(statusCode) _, err = writer.Write(body) require.NoError(t, err) } func TestClientCloseIsNilSafe(t *testing.T) { t.Parallel() var nilClient *Client require.NoError(t, nilClient.Close()) }