Skip to content

Commit c1d025f

Browse files
authored
Merge pull request #2 from godon-dev/ci_cli_mock_test
pr ci include test runs against api mock
2 parents 642f283 + 8801245 commit c1d025f

7 files changed

Lines changed: 227 additions & 73 deletions

File tree

.github/workflows/ci.yml

Lines changed: 133 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ jobs:
3232
3333
# Set environment variables for this session
3434
export SSL_CERT_FILE="/etc/ssl/certs/ca-bundle.crt"
35-
export NIX_SSL_CERT_FILE="/etc/ssl/certs/ca-bundle.crt"
35+
export NIX_SSL_CERT_FILE="/etc/ssl/certs/ca-bundle.crt"
3636
export CURL_CA_BUNDLE="/etc/ssl/certs/ca-bundle.crt"
3737
3838
# Add to nix.conf for daemon
@@ -58,3 +58,135 @@ jobs:
5858
ls -la $(nix path-info .#default)/bin/ || echo "$out/bin not found"
5959
echo "Testing compiled binary with direct path..."
6060
$(nix path-info .#default)/bin/godon_cli --help
61+
62+
- name: Start Prism mock container
63+
run: |
64+
# Download the OpenAPI spec with retry
65+
echo "Downloading OpenAPI spec..."
66+
curl -L --retry 3 --retry-delay 5 -o openapi.yml \
67+
https://raw.githubusercontent.com/godon-dev/godon-images/main/images/godon-api/openapi.yml
68+
69+
# Verify the spec was downloaded
70+
if [ ! -f openapi.yml ]; then
71+
echo "Failed to download OpenAPI spec"
72+
exit 1
73+
fi
74+
echo "OpenAPI spec downloaded successfully"
75+
echo "=== Spec file size ==="
76+
wc -l openapi.yml
77+
echo "=== First 20 lines of spec ==="
78+
head -20 openapi.yml
79+
echo "=== Paths section ==="
80+
grep -A 20 "^paths:" openapi.yml || echo "No paths section found"
81+
echo "=== All path definitions ==="
82+
grep -E "^\s*/" openapi.yml || echo "No path definitions found"
83+
84+
# Start Prism container in background
85+
docker run -d --name prism -p 4010:4010 \
86+
-v $(pwd)/openapi.yml:/tmp/openapi.yml \
87+
stoplight/prism:4 \
88+
mock --host 0.0.0.0 /tmp/openapi.yml
89+
90+
# Check if container started
91+
echo "Checking container status..."
92+
docker ps | grep prism || echo "Container not found in docker ps"
93+
echo "=== Container logs ==="
94+
docker logs prism || echo "No logs available"
95+
echo "=== Container inspection ==="
96+
docker inspect prism | grep -A 10 -B 10 "State" || echo "Could not inspect container"
97+
echo "=== Network ports ==="
98+
docker port prism || echo "Could not get port mappings"
99+
100+
# Wait for Prism to start
101+
echo "Waiting for Prism to start..."
102+
sleep 15
103+
104+
# Test basic connectivity
105+
echo "Testing basic connectivity to localhost:4010..."
106+
curl -v http://localhost:4010 || echo "Basic connection failed"
107+
108+
# Verify Prism is running with retry
109+
for i in {1..5}; do
110+
echo "=== Attempt $i: Testing Prism health ==="
111+
if curl -v http://localhost:4010/health 2>&1 | head -10; then
112+
echo "✅ Prism container started successfully on port 4010"
113+
break
114+
else
115+
echo "❌ Attempt $i: Prism not ready yet, waiting..."
116+
echo "Container still running?"
117+
docker ps | grep prism || echo "Container stopped"
118+
echo "Recent logs:"
119+
docker logs --tail 5 prism
120+
sleep 5
121+
fi
122+
done
123+
124+
# Test available endpoints
125+
echo "=== Testing available Prism endpoints ==="
126+
echo "Testing root path:"
127+
curl -v http://localhost:4010/ 2>&1 | head -10 || echo "Root path failed"
128+
129+
echo "Testing /v0 path:"
130+
curl -v http://localhost:4010/v0 2>&1 | head -10 || echo "/v0 path failed"
131+
132+
echo "Testing /v0/breeders:"
133+
curl -v http://localhost:4010/v0/breeders 2>&1 | head -10 || echo "/v0/breeders failed"
134+
135+
echo "Testing OpenAPI spec:"
136+
curl -v http://localhost:4010/openapi.json 2>&1 | head -5 || echo "OpenAPI spec failed"
137+
138+
echo "=== Testing exact URLs that CLI will use ==="
139+
echo "Testing /breeders (what CLI should call):"
140+
curl -v http://localhost:4010/breeders 2>&1 | head -10 || echo "/breeders failed"
141+
142+
echo "Testing POST /breeders:"
143+
curl -X POST -H "Content-Type: application/json" -d '{"name":"test"}' \
144+
http://localhost:4010/breeders 2>&1 | head -10 || echo "POST /breeders failed"
145+
146+
- name: Integration tests against Prism mock
147+
run: |
148+
# Get the binary path
149+
BINARY_PATH=$(nix path-info .#default)/bin/godon_cli
150+
151+
echo "Running integration tests against Prism mock..."
152+
153+
# Test breeder list
154+
echo "Testing: breeder list"
155+
$BINARY_PATH --hostname=localhost --port=4010 breeder list
156+
157+
# Create test YAML files using echo
158+
echo 'name: "Test Breeder"' > test_breeder.yml
159+
echo 'config: >' >> test_breeder.yml
160+
echo ' {"setting1": "value1", "setting2": 42}' >> test_breeder.yml
161+
162+
echo "Testing: breeder create"
163+
$BINARY_PATH --hostname=localhost --port=4010 breeder create --file=test_breeder.yml
164+
165+
# Test breeder show with a mock UUID
166+
echo "Testing: breeder show"
167+
$BINARY_PATH --hostname=localhost --port=4010 breeder show --id=550e8400-e29b-41d4-a716-446655440000
168+
169+
# Create update test file
170+
echo 'uuid: "550e8400-e29b-41d4-a716-446655440000"' > test_breeder_update.yml
171+
echo 'name: "Updated Test Breeder"' >> test_breeder_update.yml
172+
echo 'description: "Updated integration test breeder"' >> test_breeder_update.yml
173+
echo 'config: >' >> test_breeder_update.yml
174+
echo ' {"setting1": "updated_value1", "setting2": 100, "new_setting": "new_value"}' >> test_breeder_update.yml
175+
176+
echo "Testing: breeder update"
177+
$BINARY_PATH --hostname=localhost --port=4010 breeder update --file=test_breeder_update.yml
178+
179+
# Test breeder purge
180+
echo "Testing: breeder purge"
181+
$BINARY_PATH --hostname=localhost --port=4010 breeder purge --id=550e8400-e29b-41d4-a716-446655440000
182+
183+
# Test help commands
184+
echo "Testing: help commands"
185+
$BINARY_PATH --help
186+
$BINARY_PATH breeder --help || true # May fail but tests argument parsing
187+
188+
- name: Cleanup Prism container
189+
if: always()
190+
run: |
191+
docker stop prism || true
192+
docker rm prism || true

flake.nix

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -38,8 +38,10 @@
3838
buildPhase = ''
3939
echo "Building godon-cli version: ${version}"
4040
41-
# Refresh package list and build
42-
nimble refresh
41+
# Refresh package list and install dependencies only
42+
nimble refresh --verbose
43+
# Install yaml dependency without building our package
44+
nimble install -y --depsOnly --verbose
4345
4446
# Build the CLI
4547
mkdir -p bin

godon_cli.nimble

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ license = "AGPL-3.0"
77

88
# Dependencies
99

10-
requires "nim >= 2.0.0"
10+
requires "nim >= 2.0.0", "yaml"
1111

1212
# Task definitions
1313

src/godon/breeder.nim

Lines changed: 31 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -2,50 +2,53 @@
22
## Implementation of breeder-related API endpoints
33

44
import std/[httpclient, json, strutils, uri]
5+
import yaml
56
import client, types
67

7-
proc listBreeders*(client: GodonClient): ApiResponse[seq[Breeder]] =
8+
proc listBreeders*(client: GodonClient): ApiResponse[seq[BreederSummary]] =
89
## List all configured breeders
910
try:
1011
let url = client.baseUrl() & "/breeders"
1112
let response = client.httpClient.get(url)
12-
result = handleResponse[seq[Breeder]](client, response)
13+
result = handleResponse[seq[BreederSummary]](client, response)
1314
except CatchableError as e:
14-
result = ApiResponse[seq[Breeder]](success: false, data: @[], error: e.msg)
15+
result = ApiResponse[seq[BreederSummary]](success: false, data: @[], error: e.msg)
1516

16-
proc createBreeder*(client: GodonClient, request: BreederCreateRequest): ApiResponse[Breeder] =
17+
proc createBreeder*(client: GodonClient, request: BreederCreateRequest): ApiResponse[BreederSummary] =
1718
## Create a new breeder
1819
try:
1920
let url = client.baseUrl() & "/breeders"
20-
let jsonData = %*request
21+
# Convert config string to JsonNode
22+
var jsonData = %*{
23+
"name": request.name,
24+
"config": parseJson(request.config)
25+
}
26+
echo "Sending JSON: ", $jsonData
27+
client.httpClient.headers = newHttpHeaders({"Content-Type": "application/json"})
2128
let response = client.httpClient.post(url, $jsonData)
22-
result = handleResponse[Breeder](client, response)
29+
result = handleResponse[BreederSummary](client, response)
2330
except CatchableError as e:
24-
result = ApiResponse[Breeder](success: false, data: default(Breeder), error: e.msg)
31+
result = ApiResponse[BreederSummary](success: false, data: default(BreederSummary), error: e.msg)
2532

2633
proc parseBreederFromYaml*(yamlContent: string): BreederCreateRequest =
27-
## Parse breeder configuration from YAML content
28-
## Note: This would require a YAML library like yaml.nim
29-
## For now, this is a placeholder for the YAML parsing logic
30-
## Users can pass JSON directly or we can add proper YAML parsing later
34+
## Parse breeder configuration from YAML content using yaml library
3135
try:
32-
let jsonNode = parseJson(yamlContent)
33-
result = jsonNode.to(BreederCreateRequest)
36+
result = yaml.loadAs[BreederCreateRequest](yamlContent)
3437
except CatchableError as e:
35-
raise newException(ValueError, "Failed to parse YAML/JSON: " & e.msg)
38+
raise newException(ValueError, "Failed to parse YAML: " & e.msg)
3639

37-
proc createBreederFromYaml*(client: GodonClient, yamlContent: string): ApiResponse[Breeder] =
40+
proc createBreederFromYaml*(client: GodonClient, yamlContent: string): ApiResponse[BreederSummary] =
3841
## Create a breeder from YAML content
3942
try:
4043
let request = parseBreederFromYaml(yamlContent)
4144
result = client.createBreeder(request)
4245
except CatchableError as e:
43-
result = ApiResponse[Breeder](success: false, data: default(Breeder), error: e.msg)
46+
result = ApiResponse[BreederSummary](success: false, data: default(BreederSummary), error: e.msg)
4447

4548
proc getBreeder*(client: GodonClient, uuid: string): ApiResponse[Breeder] =
4649
## Get breeder details by UUID
4750
try:
48-
let url = client.baseUrl() & "/breeder?uuid=" & encodeUrl(uuid)
51+
let url = client.baseUrl() & "/breeders/" & encodeUrl(uuid)
4952
let response = client.httpClient.get(url)
5053
result = handleResponse[Breeder](client, response)
5154
except CatchableError as e:
@@ -54,8 +57,14 @@ proc getBreeder*(client: GodonClient, uuid: string): ApiResponse[Breeder] =
5457
proc updateBreeder*(client: GodonClient, request: BreederUpdateRequest): ApiResponse[Breeder] =
5558
## Update an existing breeder
5659
try:
57-
let url = client.baseUrl() & "/breeders"
58-
let jsonData = %*request
60+
let url = client.baseUrl() & "/breeders/" & encodeUrl(request.uuid)
61+
# Convert config string to JsonNode
62+
var jsonData = %*{
63+
"name": request.name,
64+
"description": request.description,
65+
"config": parseJson(request.config)
66+
}
67+
client.httpClient.headers = newHttpHeaders({"Content-Type": "application/json"})
5968
let response = client.httpClient.put(url, $jsonData)
6069
result = handleResponse[Breeder](client, response)
6170
except CatchableError as e:
@@ -64,10 +73,9 @@ proc updateBreeder*(client: GodonClient, request: BreederUpdateRequest): ApiResp
6473
proc parseBreederUpdateFromYaml*(yamlContent: string): BreederUpdateRequest =
6574
## Parse breeder update configuration from YAML content
6675
try:
67-
let jsonNode = parseJson(yamlContent)
68-
result = jsonNode.to(BreederUpdateRequest)
76+
result = yaml.loadAs[BreederUpdateRequest](yamlContent)
6977
except CatchableError as e:
70-
raise newException(ValueError, "Failed to parse YAML/JSON: " & e.msg)
78+
raise newException(ValueError, "Failed to parse YAML: " & e.msg)
7179

7280
proc updateBreederFromYaml*(client: GodonClient, yamlContent: string): ApiResponse[Breeder] =
7381
## Update a breeder from YAML content
@@ -80,7 +88,7 @@ proc updateBreederFromYaml*(client: GodonClient, yamlContent: string): ApiRespon
8088
proc deleteBreeder*(client: GodonClient, uuid: string): ApiResponse[JsonNode] =
8189
## Delete/purge a breeder by UUID
8290
try:
83-
let url = client.baseUrl() & "/breeder?uuid=" & encodeUrl(uuid)
91+
let url = client.baseUrl() & "/breeders/" & encodeUrl(uuid)
8492
let response = client.httpClient.delete(url)
8593
result = handleResponse[JsonNode](client, response)
8694
except CatchableError as e:

src/godon/client.nim

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -29,32 +29,34 @@ proc newGodonClient*(hostname: string = DefaultHostname,
2929

3030
proc baseUrl*(client: GodonClient): string =
3131
## Get the base URL for API requests
32-
result = "http://" & client.config.hostname & ":" & $client.config.port & "/" & client.config.apiVersion
32+
result = "http://" & client.config.hostname & ":" & $client.config.port
3333

3434
proc handleResponse*[T](client: GodonClient; response: Response): ApiResponse[T] =
3535
## Handle HTTP response and convert to ApiResponse
36-
let statusCode = parseInt(response.status)
36+
let statusCode = parseInt(split(response.status, " ")[0])
3737
if statusCode >= 200 and statusCode < 300:
3838
try:
39+
echo "Raw response body: ", response.body
3940
let jsonData = parseJson(response.body)
4041
result = ApiResponse[T](success: true, data: jsonData.to(T), error: "")
4142
except CatchableError as e:
4243
result = ApiResponse[T](success: false, data: default(T), error: "JSON parse error: " & e.msg)
4344
else:
4445
try:
46+
echo "HTTP Error Response Body: ", response.body
4547
let errorJson = parseJson(response.body)
46-
let errorMsg = errorJson.getOrDefault("message").getStr("HTTP Error: " & $statusCode)
48+
let errorMsg = errorJson{"message"}.getStr("HTTP Error: " & $statusCode)
4749
result = ApiResponse[T](success: false, data: default(T), error: errorMsg)
4850
except CatchableError:
4951
result = ApiResponse[T](success: false, data: default(T), error: "HTTP Error: " & $statusCode)
5052

5153
proc handleError*(client: GodonClient, response: Response): ref CatchableError =
5254
## Convert HTTP error response to exception
53-
let statusCode = parseInt(response.status)
55+
let statusCode = parseInt(split(response.status, " ")[0])
5456
var errorMsg = "HTTP Error: " & $statusCode
5557
try:
5658
let errorJson = parseJson(response.body)
57-
errorMsg = errorJson.getOrDefault("message").getStr(errorMsg)
59+
errorMsg = errorJson{"message"}.getStr(errorMsg)
5860
except CatchableError:
5961
discard
6062

src/godon/types.nim

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,24 +4,28 @@
44
import std/json
55

66
type
7+
BreederSummary* = object
8+
id*: string
9+
name*: string
10+
status*: string
11+
createdAt*: string
12+
713
Breeder* = object
8-
uuid*: string
14+
id*: string
915
name*: string
10-
description*: string
16+
status*: string
1117
config*: JsonNode
1218
createdAt*: string
13-
updatedAt*: string
1419

1520
BreederCreateRequest* = object
1621
name*: string
17-
description*: string
18-
config*: JsonNode
22+
config*: string
1923

2024
BreederUpdateRequest* = object
2125
uuid*: string
2226
name*: string
2327
description*: string
24-
config*: JsonNode
28+
config*: string
2529

2630
ApiConfig* = object
2731
hostname*: string

0 commit comments

Comments
 (0)