Skip to content

Commit 16bb75c

Browse files
chore: system db tests and fixes
1 parent a32d0c0 commit 16bb75c

File tree

4 files changed

+454
-2
lines changed

4 files changed

+454
-2
lines changed
Lines changed: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,120 @@
1+
name: Go Tests (with Database)
2+
3+
on:
4+
push:
5+
branches: [main]
6+
pull_request:
7+
branches: [main]
8+
paths:
9+
- 'apps/workspace-engine/**'
10+
- '.github/workflows/golang-tests-with-db.yaml'
11+
12+
jobs:
13+
workspace-engine-tests:
14+
runs-on: ubuntu-latest
15+
16+
# Add PostgreSQL service for integration tests
17+
services:
18+
postgres:
19+
image: postgres:18
20+
env:
21+
POSTGRES_USER: ctrlplane_test
22+
POSTGRES_PASSWORD: test_password
23+
POSTGRES_DB: ctrlplane_test
24+
ports:
25+
- 5432:5432
26+
options: >-
27+
--health-cmd pg_isready
28+
--health-interval 10s
29+
--health-timeout 5s
30+
--health-retries 5
31+
32+
defaults:
33+
run:
34+
working-directory: apps/workspace-engine
35+
36+
steps:
37+
- name: Checkout code
38+
uses: actions/checkout@v3
39+
40+
- name: Set up Go
41+
uses: actions/setup-go@v4
42+
with:
43+
go-version: "1.24"
44+
45+
- name: Cache Go modules
46+
uses: actions/cache@v3
47+
with:
48+
path: |
49+
~/.cache/go-build
50+
~/go/pkg/mod
51+
key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }}
52+
restore-keys: |
53+
${{ runner.os }}-go-
54+
55+
- name: Install dependencies
56+
run: go mod download
57+
58+
- name: Wait for PostgreSQL
59+
run: |
60+
until pg_isready -h localhost -p 5432 -U ctrlplane_test; do
61+
echo "Waiting for postgres..."
62+
sleep 2
63+
done
64+
65+
- name: Run migrations
66+
env:
67+
POSTGRES_URL: "postgresql://ctrlplane_test:test_password@localhost:5432/ctrlplane_test?sslmode=disable"
68+
run: |
69+
# TODO: Run your migrations here
70+
# Example: go run ../../packages/db/migrate.ts
71+
echo "Migrations would run here"
72+
73+
- name: Run all tests with coverage
74+
env:
75+
POSTGRES_URL: "postgresql://ctrlplane_test:test_password@localhost:5432/ctrlplane_test?sslmode=disable"
76+
POSTGRES_APPLICATION_NAME: "workspace-engine-test"
77+
run: go test -v -race -coverprofile=coverage.out -covermode=atomic -coverpkg=./... ./...
78+
79+
- name: Run DB package tests specifically
80+
env:
81+
POSTGRES_URL: "postgresql://ctrlplane_test:test_password@localhost:5432/ctrlplane_test?sslmode=disable"
82+
run: go test -v -race ./pkg/db/...
83+
84+
- name: Generate coverage report
85+
run: go tool cover -html=coverage.out -o coverage.html
86+
87+
- name: Calculate coverage percentage
88+
id: coverage
89+
run: |
90+
COVERAGE=$(go tool cover -func=coverage.out | grep total | awk '{print $3}')
91+
echo "percentage=$COVERAGE" >> $GITHUB_OUTPUT
92+
echo "Total coverage: $COVERAGE"
93+
94+
- name: Upload coverage report
95+
uses: actions/upload-artifact@v4
96+
with:
97+
name: coverage-report
98+
path: |
99+
apps/workspace-engine/coverage.out
100+
apps/workspace-engine/coverage.html
101+
102+
- name: Comment coverage on PR
103+
if: github.event_name == 'pull_request'
104+
uses: actions/github-script@v6
105+
with:
106+
script: |
107+
const coverage = '${{ steps.coverage.outputs.percentage }}';
108+
const comment = `## 📊 Code Coverage Report (with DB Integration Tests)
109+
110+
**workspace-engine** coverage: \`${coverage}\`
111+
112+
[View detailed coverage report in artifacts](https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }})`;
113+
114+
github.rest.issues.createComment({
115+
issue_number: context.issue.number,
116+
owner: context.repo.owner,
117+
repo: context.repo.repo,
118+
body: comment
119+
});
120+

apps/workspace-engine/pkg/db/systems.go

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -56,11 +56,12 @@ func scanSystemRow(rows pgx.Rows) (*oapi.System, error) {
5656
}
5757

5858
const SYSTEM_UPSERT_QUERY = `
59-
INSERT INTO system (id, workspace_id, name, description)
60-
VALUES ($1, $2, $3, $4)
59+
INSERT INTO system (id, workspace_id, name, slug, description)
60+
VALUES ($1, $2, $3, $4, $5)
6161
ON CONFLICT (id) DO UPDATE SET
6262
workspace_id = EXCLUDED.workspace_id,
6363
name = EXCLUDED.name,
64+
slug = EXCLUDED.slug,
6465
description = EXCLUDED.description
6566
`
6667

@@ -71,6 +72,7 @@ func writeSystem(ctx context.Context, system *oapi.System, tx pgx.Tx) error {
7172
system.Id,
7273
system.WorkspaceId,
7374
system.Name,
75+
system.Name,
7476
system.Description,
7577
); err != nil {
7678
return err
Lines changed: 226 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,226 @@
1+
package db
2+
3+
import (
4+
"fmt"
5+
"strings"
6+
"testing"
7+
"workspace-engine/pkg/oapi"
8+
9+
"github.com/google/uuid"
10+
)
11+
12+
func validateRetrievedSystems(t *testing.T, actualSystems []*oapi.System, expectedSystems []*oapi.System) {
13+
if len(actualSystems) != len(expectedSystems) {
14+
t.Fatalf("expected %d systems, got %d", len(expectedSystems), len(actualSystems))
15+
}
16+
for _, expectedSystem := range expectedSystems {
17+
var actualSystem *oapi.System
18+
for _, as := range actualSystems {
19+
if as.Id == expectedSystem.Id {
20+
actualSystem = as
21+
break
22+
}
23+
}
24+
25+
if actualSystem == nil {
26+
t.Fatalf("expected system %v, got %v", expectedSystem, actualSystem)
27+
}
28+
if actualSystem.Id != expectedSystem.Id {
29+
t.Fatalf("expected system %v, got %v", expectedSystem, actualSystem)
30+
}
31+
if actualSystem.Name != expectedSystem.Name {
32+
t.Fatalf("expected system %v, got %v", expectedSystem, actualSystem)
33+
}
34+
compareStrPtr(t, actualSystem.Description, expectedSystem.Description)
35+
if actualSystem.WorkspaceId != expectedSystem.WorkspaceId {
36+
t.Fatalf("expected system %v, got %v", expectedSystem, actualSystem)
37+
}
38+
}
39+
}
40+
41+
func TestDBSystems_BasicWrite(t *testing.T) {
42+
workspaceID, conn := setupTestWithWorkspace(t)
43+
// Note: conn.Release() is handled by t.Cleanup in setupTestWithWorkspace
44+
45+
tx, err := conn.Begin(t.Context())
46+
if err != nil {
47+
t.Fatalf("failed to begin tx: %v", err)
48+
}
49+
defer tx.Rollback(t.Context())
50+
51+
id := uuid.New().String()
52+
name := fmt.Sprintf("test-system-%s", id[:8])
53+
description := fmt.Sprintf("test-description-%s", id[:8])
54+
sys := &oapi.System{
55+
Id: id,
56+
WorkspaceId: workspaceID,
57+
Name: name,
58+
Description: &description,
59+
}
60+
61+
err = writeSystem(t.Context(), sys, tx)
62+
if err != nil {
63+
t.Fatalf("expected no errors, got %v", err)
64+
}
65+
66+
err = tx.Commit(t.Context())
67+
if err != nil {
68+
t.Fatalf("failed to commit: %v", err)
69+
}
70+
71+
expectedSystems := []*oapi.System{sys}
72+
actualSystems, err := getSystems(t.Context(), workspaceID)
73+
if err != nil {
74+
t.Fatalf("expected no errors, got %v", err)
75+
}
76+
77+
validateRetrievedSystems(t, actualSystems, expectedSystems)
78+
}
79+
80+
func TestDBSystems_BasicWriteAndDelete(t *testing.T) {
81+
workspaceID, conn := setupTestWithWorkspace(t)
82+
// Note: conn.Release() is handled by t.Cleanup in setupTestWithWorkspace
83+
84+
tx, err := conn.Begin(t.Context())
85+
if err != nil {
86+
t.Fatalf("failed to begin tx: %v", err)
87+
}
88+
defer tx.Rollback(t.Context())
89+
90+
id := uuid.New().String()
91+
name := fmt.Sprintf("test-system-%s", id[:8])
92+
description := fmt.Sprintf("test-description-%s", id[:8])
93+
sys := &oapi.System{
94+
Id: id,
95+
WorkspaceId: workspaceID,
96+
Name: name,
97+
Description: &description,
98+
}
99+
100+
err = writeSystem(t.Context(), sys, tx)
101+
if err != nil {
102+
t.Fatalf("expected no errors, got %v", err)
103+
}
104+
105+
err = tx.Commit(t.Context())
106+
if err != nil {
107+
t.Fatalf("failed to commit: %v", err)
108+
}
109+
110+
tx, err = conn.Begin(t.Context())
111+
if err != nil {
112+
t.Fatalf("failed to begin tx: %v", err)
113+
}
114+
defer tx.Rollback(t.Context())
115+
116+
expectedSystems := []*oapi.System{sys}
117+
actualSystems, err := getSystems(t.Context(), workspaceID)
118+
if err != nil {
119+
t.Fatalf("expected no errors, got %v", err)
120+
}
121+
122+
validateRetrievedSystems(t, actualSystems, expectedSystems)
123+
124+
err = deleteSystem(t.Context(), id, tx)
125+
if err != nil {
126+
t.Fatalf("expected no errors, got %v", err)
127+
}
128+
129+
err = tx.Commit(t.Context())
130+
if err != nil {
131+
t.Fatalf("failed to commit: %v", err)
132+
}
133+
134+
actualSystems, err = getSystems(t.Context(), workspaceID)
135+
if err != nil {
136+
t.Fatalf("expected no errors, got %v", err)
137+
}
138+
139+
validateRetrievedSystems(t, actualSystems, []*oapi.System{})
140+
}
141+
142+
func TestDBSystems_BasicWriteAndUpdate(t *testing.T) {
143+
workspaceID, conn := setupTestWithWorkspace(t)
144+
// Note: conn.Release() is handled by t.Cleanup in setupTestWithWorkspace
145+
146+
tx, err := conn.Begin(t.Context())
147+
if err != nil {
148+
t.Fatalf("failed to begin tx: %v", err)
149+
}
150+
defer tx.Rollback(t.Context())
151+
152+
id := uuid.New().String()
153+
name := fmt.Sprintf("test-system-%s", id[:8])
154+
description := fmt.Sprintf("test-description-%s", id[:8])
155+
sys := &oapi.System{
156+
Id: id,
157+
WorkspaceId: workspaceID,
158+
Name: name,
159+
Description: &description,
160+
}
161+
162+
err = writeSystem(t.Context(), sys, tx)
163+
if err != nil {
164+
t.Fatalf("expected no errors, got %v", err)
165+
}
166+
167+
err = tx.Commit(t.Context())
168+
if err != nil {
169+
t.Fatalf("failed to commit: %v", err)
170+
}
171+
172+
tx, err = conn.Begin(t.Context())
173+
if err != nil {
174+
t.Fatalf("failed to begin tx: %v", err)
175+
}
176+
defer tx.Rollback(t.Context())
177+
178+
sys.Description = &description
179+
err = writeSystem(t.Context(), sys, tx)
180+
if err != nil {
181+
t.Fatalf("expected no errors, got %v", err)
182+
}
183+
184+
err = tx.Commit(t.Context())
185+
if err != nil {
186+
t.Fatalf("failed to commit: %v", err)
187+
}
188+
189+
actualSystems, err := getSystems(t.Context(), workspaceID)
190+
if err != nil {
191+
t.Fatalf("expected no errors, got %v", err)
192+
}
193+
194+
validateRetrievedSystems(t, actualSystems, []*oapi.System{sys})
195+
}
196+
197+
func TestDBSystems_NonexistentWorkspaceThrowsError(t *testing.T) {
198+
_, conn := setupTestWithWorkspace(t)
199+
// Note: conn.Release() is handled by t.Cleanup in setupTestWithWorkspace
200+
201+
tx, err := conn.Begin(t.Context())
202+
if err != nil {
203+
t.Fatalf("failed to begin tx: %v", err)
204+
}
205+
defer tx.Rollback(t.Context())
206+
207+
description := "test-description"
208+
sys := &oapi.System{
209+
Id: uuid.New().String(),
210+
WorkspaceId: uuid.New().String(),
211+
Name: "test-system",
212+
Description: &description,
213+
}
214+
215+
err = writeSystem(t.Context(), sys, tx)
216+
// should throw fk constraint error
217+
if err == nil {
218+
t.Fatalf("expected FK violation error, got nil")
219+
}
220+
221+
// Check for foreign key violation (SQLSTATE 23503)
222+
if !strings.Contains(err.Error(), "23503") && !strings.Contains(err.Error(), "foreign key") {
223+
t.Fatalf("expected FK violation error, got: %v", err)
224+
}
225+
226+
}

0 commit comments

Comments
 (0)