// ABOUTME: Unit tests for App management APIs using httptest mock server // ABOUTME: Tests create, show, list, and delete operations with error conditions package edgeconnect import ( "context" "encoding/json" "net/http" "net/http/httptest" "testing" "time" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) func TestCreateApp(t *testing.T) { tests := []struct { name string input *NewAppInput mockStatusCode int mockResponse string expectError bool }{ { name: "successful creation", input: &NewAppInput{ Region: "us-west", App: App{ Key: AppKey{ Organization: "testorg", Name: "testapp", Version: "1.0.0", }, Deployment: "kubernetes", }, }, mockStatusCode: 200, mockResponse: `{"message": "success"}`, expectError: false, }, { name: "validation error", input: &NewAppInput{ Region: "us-west", App: App{ Key: AppKey{ Organization: "", Name: "testapp", Version: "1.0.0", }, }, }, mockStatusCode: 400, mockResponse: `{"message": "organization is required"}`, expectError: true, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { // Create mock server server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { assert.Equal(t, "POST", r.Method) assert.Equal(t, "/api/v1/auth/ctrl/CreateApp", r.URL.Path) assert.Equal(t, "application/json", r.Header.Get("Content-Type")) w.WriteHeader(tt.mockStatusCode) _, _ = w.Write([]byte(tt.mockResponse)) })) defer server.Close() // Create client client := NewClient(server.URL, WithHTTPClient(&http.Client{Timeout: 5 * time.Second}), WithAuthProvider(NewStaticTokenProvider("test-token")), ) // Execute test ctx := context.Background() err := client.CreateApp(ctx, tt.input) // Verify results if tt.expectError { assert.Error(t, err) } else { assert.NoError(t, err) } }) } } func TestShowApp(t *testing.T) { tests := []struct { name string appKey AppKey region string mockStatusCode int mockResponse string expectError bool expectNotFound bool }{ { name: "successful show", appKey: AppKey{ Organization: "testorg", Name: "testapp", Version: "1.0.0", }, region: "us-west", mockStatusCode: 200, mockResponse: `{"data": {"key": {"organization": "testorg", "name": "testapp", "version": "1.0.0"}, "deployment": "kubernetes"}} `, expectError: false, expectNotFound: false, }, { name: "app not found", appKey: AppKey{ Organization: "testorg", Name: "nonexistent", Version: "1.0.0", }, region: "us-west", mockStatusCode: 404, mockResponse: "", expectError: true, expectNotFound: true, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { // Create mock server server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { assert.Equal(t, "POST", r.Method) assert.Equal(t, "/api/v1/auth/ctrl/ShowApp", r.URL.Path) w.WriteHeader(tt.mockStatusCode) if tt.mockResponse != "" { _, _ = w.Write([]byte(tt.mockResponse)) } })) defer server.Close() // Create client client := NewClient(server.URL, WithHTTPClient(&http.Client{Timeout: 5 * time.Second}), ) // Execute test ctx := context.Background() app, err := client.ShowApp(ctx, tt.appKey, tt.region) // Verify results if tt.expectError { assert.Error(t, err) if tt.expectNotFound { assert.Contains(t, err.Error(), "resource not found") } } else { require.NoError(t, err) assert.Equal(t, tt.appKey.Organization, app.Key.Organization) assert.Equal(t, tt.appKey.Name, app.Key.Name) assert.Equal(t, tt.appKey.Version, app.Key.Version) } }) } } func TestShowApps(t *testing.T) { server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { assert.Equal(t, "POST", r.Method) assert.Equal(t, "/api/v1/auth/ctrl/ShowApp", r.URL.Path) // Verify request body var filter AppFilter err := json.NewDecoder(r.Body).Decode(&filter) require.NoError(t, err) assert.Equal(t, "testorg", filter.App.Key.Organization) assert.Equal(t, "us-west", filter.Region) // Return multiple apps response := `{"data": {"key": {"organization": "testorg", "name": "app1", "version": "1.0.0"}, "deployment": "kubernetes"}} {"data": {"key": {"organization": "testorg", "name": "app2", "version": "1.0.0"}, "deployment": "docker"}} ` w.WriteHeader(200) _, _ = w.Write([]byte(response)) })) defer server.Close() client := NewClient(server.URL) ctx := context.Background() apps, err := client.ShowApps(ctx, AppKey{Organization: "testorg"}, "us-west") require.NoError(t, err) assert.Len(t, apps, 2) assert.Equal(t, "app1", apps[0].Key.Name) assert.Equal(t, "app2", apps[1].Key.Name) } func TestUpdateApp(t *testing.T) { tests := []struct { name string input *UpdateAppInput mockStatusCode int mockResponse string expectError bool }{ { name: "successful update", input: &UpdateAppInput{ Region: "us-west", App: App{ Key: AppKey{ Organization: "testorg", Name: "testapp", Version: "1.0.0", }, Deployment: "kubernetes", ImagePath: "nginx:latest", }, }, mockStatusCode: 200, mockResponse: `{"message": "success"}`, expectError: false, }, { name: "validation error", input: &UpdateAppInput{ Region: "us-west", App: App{ Key: AppKey{ Organization: "", Name: "testapp", Version: "1.0.0", }, }, }, mockStatusCode: 400, mockResponse: `{"message": "organization is required"}`, expectError: true, }, { name: "app not found", input: &UpdateAppInput{ Region: "us-west", App: App{ Key: AppKey{ Organization: "testorg", Name: "nonexistent", Version: "1.0.0", }, }, }, mockStatusCode: 404, mockResponse: `{"message": "app not found"}`, expectError: true, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { // Create mock server server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { assert.Equal(t, "POST", r.Method) assert.Equal(t, "/api/v1/auth/ctrl/UpdateApp", r.URL.Path) assert.Equal(t, "application/json", r.Header.Get("Content-Type")) // Verify request body var input UpdateAppInput err := json.NewDecoder(r.Body).Decode(&input) require.NoError(t, err) assert.Equal(t, tt.input.Region, input.Region) assert.Equal(t, tt.input.App.Key.Organization, input.App.Key.Organization) w.WriteHeader(tt.mockStatusCode) _, _ = w.Write([]byte(tt.mockResponse)) })) defer server.Close() // Create client client := NewClient(server.URL, WithHTTPClient(&http.Client{Timeout: 5 * time.Second}), WithAuthProvider(NewStaticTokenProvider("test-token")), ) // Execute test ctx := context.Background() err := client.UpdateApp(ctx, tt.input) // Verify results if tt.expectError { assert.Error(t, err) } else { assert.NoError(t, err) } }) } } func TestDeleteApp(t *testing.T) { tests := []struct { name string appKey AppKey region string mockStatusCode int expectError bool }{ { name: "successful deletion", appKey: AppKey{ Organization: "testorg", Name: "testapp", Version: "1.0.0", }, region: "us-west", mockStatusCode: 200, expectError: false, }, { name: "already deleted (404 ok)", appKey: AppKey{ Organization: "testorg", Name: "testapp", Version: "1.0.0", }, region: "us-west", mockStatusCode: 404, expectError: false, }, { name: "server error", appKey: AppKey{ Organization: "testorg", Name: "testapp", Version: "1.0.0", }, region: "us-west", mockStatusCode: 500, expectError: true, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { assert.Equal(t, "POST", r.Method) assert.Equal(t, "/api/v1/auth/ctrl/DeleteApp", r.URL.Path) w.WriteHeader(tt.mockStatusCode) })) defer server.Close() client := NewClient(server.URL) ctx := context.Background() err := client.DeleteApp(ctx, tt.appKey, tt.region) if tt.expectError { assert.Error(t, err) } else { assert.NoError(t, err) } }) } } func TestClientOptions(t *testing.T) { t.Run("with auth provider", func(t *testing.T) { authProvider := NewStaticTokenProvider("test-token") client := NewClient("https://example.com", WithAuthProvider(authProvider), ) assert.Equal(t, authProvider, client.AuthProvider) }) t.Run("with custom HTTP client", func(t *testing.T) { httpClient := &http.Client{Timeout: 10 * time.Second} client := NewClient("https://example.com", WithHTTPClient(httpClient), ) assert.Equal(t, httpClient, client.HTTPClient) }) t.Run("with retry options", func(t *testing.T) { retryOpts := RetryOptions{MaxRetries: 5} client := NewClient("https://example.com", WithRetryOptions(retryOpts), ) assert.Equal(t, 5, client.RetryOpts.MaxRetries) }) } func TestAPIError(t *testing.T) { err := &APIError{ StatusCode: 400, Messages: []string{"validation failed", "name is required"}, } assert.Contains(t, err.Error(), "validation failed") assert.Equal(t, 400, err.StatusCode) assert.Len(t, err.Messages, 2) }