Skip to content

Commit 434c8e2

Browse files
committed
pr ci include test runs against api mock
Using openapi.yml godon-api mock deliberately. That avoids having to start godon-api dependencies and keeps the test flow simple.
1 parent ef5abe4 commit 434c8e2

7 files changed

Lines changed: 190 additions & 50 deletions

File tree

.github/workflows/ci.yml

Lines changed: 136 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,138 @@ 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 'description: "Integration test breeder"' >> test_breeder.yml
160+
echo 'config:' >> test_breeder.yml
161+
echo ' setting1: "value1"' >> test_breeder.yml
162+
echo ' setting2: 42' >> test_breeder.yml
163+
164+
echo "Testing: breeder create"
165+
$BINARY_PATH --hostname=localhost --port=4010 breeder create --file=test_breeder.yml
166+
167+
# Test breeder show with a mock UUID
168+
echo "Testing: breeder show"
169+
$BINARY_PATH --hostname=localhost --port=4010 breeder show --id=123e4567-e89b-12d3-a456-426614174000
170+
171+
# Create update test file
172+
echo 'name: "Updated Test Breeder"' > test_breeder_update.yml
173+
echo 'description: "Updated integration test breeder"' >> test_breeder_update.yml
174+
echo 'config:' >> test_breeder_update.yml
175+
echo ' setting1: "updated_value1"' >> test_breeder_update.yml
176+
echo ' setting2: 100' >> test_breeder_update.yml
177+
echo ' new_setting: "new_value"' >> test_breeder_update.yml
178+
179+
echo "Testing: breeder update"
180+
$BINARY_PATH --hostname=localhost --port=4010 breeder update --file=test_breeder_update.yml
181+
182+
# Test breeder purge
183+
echo "Testing: breeder purge"
184+
$BINARY_PATH --hostname=localhost --port=4010 breeder purge --id=123e4567-e89b-12d3-a456-426614174000
185+
186+
# Test help commands
187+
echo "Testing: help commands"
188+
$BINARY_PATH --help
189+
$BINARY_PATH breeder --help || true # May fail but tests argument parsing
190+
191+
- name: Cleanup Prism container
192+
if: always()
193+
run: |
194+
docker stop prism || true
195+
docker rm prism || true

flake.nix

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,8 +38,9 @@
3838
buildPhase = ''
3939
echo "Building godon-cli version: ${version}"
4040
41-
# Refresh package list and build
41+
# Refresh package list and install dependencies
4242
nimble refresh
43+
nimble install -y
4344
4445
# Build the CLI
4546
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: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
## Implementation of breeder-related API endpoints
33

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

78
proc listBreeders*(client: GodonClient): ApiResponse[seq[Breeder]] =
@@ -24,15 +25,12 @@ proc createBreeder*(client: GodonClient, request: BreederCreateRequest): ApiResp
2425
result = ApiResponse[Breeder](success: false, data: default(Breeder), error: e.msg)
2526

2627
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
28+
## Parse breeder configuration from YAML content using yaml library
3129
try:
32-
let jsonNode = parseJson(yamlContent)
33-
result = jsonNode.to(BreederCreateRequest)
30+
let yamlNode = yaml.loadYaml(yamlContent)
31+
result = yamlNode.to(BreederCreateRequest)
3432
except CatchableError as e:
35-
raise newException(ValueError, "Failed to parse YAML/JSON: " & e.msg)
33+
raise newException(ValueError, "Failed to parse YAML: " & e.msg)
3634

3735
proc createBreederFromYaml*(client: GodonClient, yamlContent: string): ApiResponse[Breeder] =
3836
## Create a breeder from YAML content

src/godon/client.nim

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -29,32 +29,33 @@ 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:
4546
let errorJson = parseJson(response.body)
46-
let errorMsg = errorJson.getOrDefault("message").getStr("HTTP Error: " & $statusCode)
47+
let errorMsg = errorJson{"message"}.getStr("HTTP Error: " & $statusCode)
4748
result = ApiResponse[T](success: false, data: default(T), error: errorMsg)
4849
except CatchableError:
4950
result = ApiResponse[T](success: false, data: default(T), error: "HTTP Error: " & $statusCode)
5051

5152
proc handleError*(client: GodonClient, response: Response): ref CatchableError =
5253
## Convert HTTP error response to exception
53-
let statusCode = parseInt(response.status)
54+
let statusCode = parseInt(split(response.status, " ")[0])
5455
var errorMsg = "HTTP Error: " & $statusCode
5556
try:
5657
let errorJson = parseJson(response.body)
57-
errorMsg = errorJson.getOrDefault("message").getStr(errorMsg)
58+
errorMsg = errorJson{"message"}.getStr(errorMsg)
5859
except CatchableError:
5960
discard
6061

src/godon/types.nim

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,11 @@ import std/json
55

66
type
77
Breeder* = object
8-
uuid*: string
8+
id*: string
99
name*: string
10-
description*: string
10+
status*: string
1111
config*: JsonNode
1212
createdAt*: string
13-
updatedAt*: string
1413

1514
BreederCreateRequest* = object
1615
name*: string

src/godon_cli.nim

Lines changed: 38 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,9 @@ Usage:
1010
Commands:
1111
breeder list List all configured breeders
1212
breeder create --file <path> Create a breeder from file
13-
breeder show --uuid <uuid> Show breeder details
13+
breeder show --id <id> Show breeder details
1414
breeder update --file <path> Update a breeder from file
15-
breeder purge --uuid <uuid> Delete a breeder
15+
breeder purge --id <id> Delete a breeder
1616
1717
Global Options:
1818
--hostname, -h <host> Godon hostname (default: localhost)
@@ -24,7 +24,7 @@ Examples:
2424
godon_cli breeder list
2525
godon_cli --hostname api.example.com --port 9090 breeder list
2626
godon_cli breeder create --file breeder.yaml
27-
godon_cli breeder show --uuid 123e4567-e89b-12d3-a456-426614174000
27+
godon_cli breeder show --id 123e4567-e89b-12d3-a456-426614174000
2828
"""
2929

3030
proc writeError(message: string) =
@@ -53,12 +53,20 @@ proc parseArgs(): (string, string, int, string, seq[string]) =
5353
of "hostname":
5454
hostname = val
5555
of "port":
56+
if val.len == 0:
57+
writeError("Port option requires a value")
5658
try:
5759
port = parseInt(val)
5860
except ValueError:
5961
writeError("Invalid port number: " & val)
6062
of "api-version":
6163
apiVersion = val
64+
of "file":
65+
# Reconstruct as argument for subcommand parsing
66+
args.add("--file=" & val)
67+
of "id":
68+
# Reconstruct as argument for subcommand parsing
69+
args.add("--id=" & val)
6270
of "help", "h":
6371
writeHelp()
6472
quit(0)
@@ -84,20 +92,19 @@ proc handleBreederCommand(client: GodonClient, command: string, args: seq[string
8492
if response.success:
8593
echo "Breeders:"
8694
for breeder in response.data:
87-
echo " UUID: ", breeder.uuid
95+
echo " ID: ", breeder.id
8896
echo " Name: ", breeder.name
89-
echo " Description: ", breeder.description
97+
echo " Status: ", breeder.status
9098
echo " Created: ", breeder.createdAt
91-
echo " Updated: ", breeder.updatedAt
9299
echo " ---"
93100
else:
94101
writeError(response.error)
95102

96103
of "create":
97104
var file = ""
98-
for i in 1 ..< args.len.int:
99-
if args[i-1] == "--file":
100-
file = args[i]
105+
for arg in args:
106+
if arg.startsWith("--file="):
107+
file = arg.split("=")[1]
101108
break
102109

103110
if file.len == 0:
@@ -111,32 +118,31 @@ proc handleBreederCommand(client: GodonClient, command: string, args: seq[string
111118
let response = client.createBreederFromYaml(content)
112119
if response.success:
113120
echo "Breeder created successfully:"
114-
echo " UUID: ", response.data.uuid
121+
echo " ID: ", response.data.id
115122
echo " Name: ", response.data.name
116-
echo " Description: ", response.data.description
123+
echo " Status: ", response.data.status
117124
else:
118125
writeError(response.error)
119126

120127
of "show":
121-
var uuid = ""
122-
for i in 1 ..< args.len.int:
123-
if args[i-1] == "--uuid":
124-
uuid = args[i]
128+
var id = ""
129+
for arg in args:
130+
if arg.startsWith("--id="):
131+
id = arg.split("=")[1]
125132
break
126133

127-
if uuid.len == 0:
128-
writeError("breeder show requires --uuid <uuid>")
134+
if id.len == 0:
135+
writeError("breeder show requires --id <id>")
129136

130-
echo "Getting breeder details for UUID: ", uuid
131-
let response = client.getBreeder(uuid)
137+
echo "Getting breeder details for ID: ", id
138+
let response = client.getBreeder(id)
132139
if response.success:
133140
echo "Breeder Details:"
134-
echo " UUID: ", response.data.uuid
141+
echo " ID: ", response.data.id
135142
echo " Name: ", response.data.name
136-
echo " Description: ", response.data.description
143+
echo " Status: ", response.data.status
137144
echo " Config: ", pretty(response.data.config)
138145
echo " Created: ", response.data.createdAt
139-
echo " Updated: ", response.data.updatedAt
140146
else:
141147
writeError(response.error)
142148

@@ -158,24 +164,24 @@ proc handleBreederCommand(client: GodonClient, command: string, args: seq[string
158164
let response = client.updateBreederFromYaml(content)
159165
if response.success:
160166
echo "Breeder updated successfully:"
161-
echo " UUID: ", response.data.uuid
167+
echo " ID: ", response.data.id
162168
echo " Name: ", response.data.name
163-
echo " Description: ", response.data.description
169+
echo " Status: ", response.data.status
164170
else:
165171
writeError(response.error)
166172

167173
of "purge":
168-
var uuid = ""
169-
for i in 1 ..< args.len.int:
170-
if args[i-1] == "--uuid":
171-
uuid = args[i]
174+
var id = ""
175+
for arg in args:
176+
if arg.startsWith("--id="):
177+
id = arg.split("=")[1]
172178
break
173179

174-
if uuid.len == 0:
175-
writeError("breeder purge requires --uuid <uuid>")
180+
if id.len == 0:
181+
writeError("breeder purge requires --id <id>")
176182

177-
echo "Deleting breeder with UUID: ", uuid
178-
let response = client.deleteBreeder(uuid)
183+
echo "Deleting breeder with ID: ", id
184+
let response = client.deleteBreeder(id)
179185
if response.success:
180186
echo "Breeder deleted successfully"
181187
if response.data != nil:

0 commit comments

Comments
 (0)