409 lines
9.7 KiB
Go
409 lines
9.7 KiB
Go
// 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)
|
|
}
|