Skip to content

Commit eca55c6

Browse files
committed
refactor(go): restore go.mod (and sum) on project root
and restore missing cypress tests
1 parent 5f05ef2 commit eca55c6

File tree

10 files changed

+785
-43
lines changed

10 files changed

+785
-43
lines changed

.github/workflows/test-backend.yml

Lines changed: 12 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -38,12 +38,10 @@ jobs:
3838
with:
3939
go-version: '1.24.5'
4040
cache: true
41-
cache-dependency-path: backend/go.sum
41+
cache-dependency-path: go.sum
4242

4343
- name: Download dependencies
44-
run: |
45-
cd backend
46-
go mod download
44+
run: go mod download
4745

4846
- name: Create empty frontend dist directory for embed directive
4947
run: |
@@ -52,17 +50,14 @@ jobs:
5250
5351
- name: Run go fmt check
5452
run: |
55-
cd backend
56-
if [ "$(gofmt -s -l . | wc -l)" -gt 0 ]; then
53+
if [ "$(gofmt -s -l backend | wc -l)" -gt 0 ]; then
5754
echo "The following files need to be formatted:"
58-
gofmt -s -l .
55+
gofmt -s -l backend
5956
exit 1
6057
fi
6158
6259
- name: Run go vet
63-
run: |
64-
cd backend
65-
go vet ./...
60+
run: go vet ./backend/...
6661

6762
- name: Run unit tests
6863
env:
@@ -71,19 +66,16 @@ jobs:
7166
ACKIFY_OAUTH_CLIENT_ID: "test-client-id"
7267
ACKIFY_OAUTH_CLIENT_SECRET: "test-client-secret"
7368
ACKIFY_OAUTH_COOKIE_SECRET: "dGVzdC1jb29raWUtc2VjcmV0LXRlc3QtY29va2llLXNlY3JldA=="
74-
run: |
75-
cd backend
76-
go test -v -race -short ./...
69+
run: go test -v -race -short ./backend/...
7770

7871
- name: Run integration tests
7972
env:
8073
ACKIFY_DB_DSN: "postgres://postgres:testpassword@localhost:5432/ackify_test?sslmode=disable"
8174
INTEGRATION_TESTS: "1"
8275
run: |
83-
cd backend
8476
go test -v -race -tags=integration -p 1 -count=1 \
85-
./internal/infrastructure/database/... \
86-
./internal/presentation/api/admin
77+
./backend/internal/infrastructure/database/... \
78+
./backend/internal/presentation/api/admin
8779
8880
- name: Generate coverage report
8981
env:
@@ -95,14 +87,13 @@ jobs:
9587
ACKIFY_OAUTH_CLIENT_SECRET: "test-client-secret"
9688
ACKIFY_OAUTH_COOKIE_SECRET: "dGVzdC1jb29raWUtc2VjcmV0LXRlc3QtY29va2llLXNlY3JldA=="
9789
run: |
98-
cd backend
9990
# Unit coverage
100-
go test -v -race -short -covermode=atomic -coverprofile=coverage-unit.out ./...
91+
go test -v -race -short -covermode=atomic -coverprofile=coverage-unit.out ./backend/...
10192
# Integration coverage
10293
go test -v -race -tags=integration -p 1 -count=1 \
10394
-covermode=atomic -coverprofile=coverage-integration.out \
104-
./internal/infrastructure/database/... \
105-
./internal/presentation/api/admin
95+
./backend/internal/infrastructure/database/... \
96+
./backend/internal/presentation/api/admin
10697
# Merge coverage
10798
echo "mode: atomic" > coverage.out
10899
tail -n +2 coverage-unit.out >> coverage.out
@@ -115,5 +106,5 @@ jobs:
115106
uses: actions/upload-artifact@v4
116107
with:
117108
name: backend-coverage
118-
path: backend/coverage.out
109+
path: coverage.out
119110
retention-days: 1

.github/workflows/test-e2e.yml

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ jobs:
4444
with:
4545
go-version: '1.24.5'
4646
cache: true
47-
cache-dependency-path: backend/go.sum
47+
cache-dependency-path: go.sum
4848

4949
- name: Setup Node.js
5050
uses: actions/setup-node@v4
@@ -64,15 +64,13 @@ jobs:
6464
run: npm run build
6565

6666
- name: Install backend dependencies
67-
working-directory: backend
6867
run: go mod download
6968

7069
- name: Run database migrations
71-
working-directory: backend
7270
env:
7371
ACKIFY_DB_DSN: "postgres://postgres:testpassword@localhost:5432/ackify_test?sslmode=disable"
7472
run: |
75-
go run ./cmd/migrate/main.go -migrations-path file://migrations up
73+
go run ./backend/cmd/migrate/main.go -migrations-path file://backend/migrations up
7674
7775
- name: Copy frontend dist for embed
7876
run: |
@@ -107,8 +105,7 @@ jobs:
107105
ACKIFY_DOCUMENT_RATE_LIMIT: "1000"
108106
ACKIFY_GENERAL_RATE_LIMIT: "1000"
109107
run: |
110-
cd backend && go build -o ../ackify ./cmd/community
111-
cd ..
108+
go build -o ackify ./backend/cmd/community
112109
./ackify > /tmp/ackify.log 2>&1 &
113110
echo $! > /tmp/ackify.pid
114111

Dockerfile

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -22,17 +22,17 @@ FROM golang:alpine AS builder
2222
RUN apk update && apk add --no-cache ca-certificates git curl && rm -rf /var/cache/apk/*
2323
RUN adduser -D -g '' ackuser
2424

25-
WORKDIR /app/backend
26-
COPY backend/go.mod backend/go.sum ./
25+
WORKDIR /app
26+
COPY go.mod go.sum ./
2727
ENV GOTOOLCHAIN=auto
2828
# Cache Go modules and build cache between builds
2929
RUN --mount=type=cache,target=/go/pkg/mod \
3030
--mount=type=cache,target=/root/.cache/go-build \
3131
go mod download && go mod verify
32-
COPY backend/ ./
32+
COPY backend/ ./backend/
3333

34-
RUN mkdir -p cmd/community/web/dist
35-
COPY --from=spa-builder /app/webapp/dist ./cmd/community/web/dist
34+
RUN mkdir -p backend/cmd/community/web/dist
35+
COPY --from=spa-builder /app/webapp/dist ./backend/cmd/community/web/dist
3636

3737
# Cross-compile per target platform
3838
ARG TARGETOS
@@ -46,14 +46,14 @@ RUN --mount=type=cache,target=/go/pkg/mod \
4646
CGO_ENABLED=0 GOOS=$TARGETOS GOARCH=$TARGETARCH go build \
4747
-a -installsuffix cgo \
4848
-ldflags="-w -s -X main.Version=${VERSION} -X main.Commit=${COMMIT} -X main.BuildDate=${BUILD_DATE}" \
49-
-o /app/ackify ./cmd/community
49+
-o /app/ackify ./backend/cmd/community
5050

5151
RUN --mount=type=cache,target=/go/pkg/mod \
5252
--mount=type=cache,target=/root/.cache/go-build \
5353
CGO_ENABLED=0 GOOS=$TARGETOS GOARCH=$TARGETARCH go build \
5454
-a -installsuffix cgo \
5555
-ldflags="-w -s" \
56-
-o /app/migrate ./cmd/migrate
56+
-o /app/migrate ./backend/cmd/migrate
5757

5858
FROM gcr.io/distroless/static-debian12:nonroot
5959

backend/go.mod renamed to go.mod

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
module github.com/btouchard/ackify-ce/backend
1+
module github.com/btouchard/ackify-ce
22

33
go 1.24.5
44

File renamed without changes.

run-tests-suite.sh

Lines changed: 7 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -19,14 +19,13 @@ echo -e "${BLUE}╔════════════════════
1919
echo ""
2020

2121
# Check if we're in the right directory
22-
if [ ! -f "backend/go.mod" ] || [ ! -d "backend" ] || [ ! -d "webapp" ]; then
22+
if [ ! -f "go.mod" ] || [ ! -d "webapp" ]; then
2323
echo -e "${RED}❌ Error: Please run this script from the project root directory${NC}"
2424
exit 1
2525
fi
2626

2727
# Variables
2828
PROJECT_ROOT=$(pwd)
29-
BACKEND_DIR="$PROJECT_ROOT/backend"
3029
WEBAPP_DIR="$PROJECT_ROOT/webapp"
3130
COVERAGE_DIR="$PROJECT_ROOT/.coverage-report"
3231
TIMESTAMP=$(date +%Y%m%d_%H%M%S)
@@ -69,7 +68,7 @@ echo -e "${CYAN} Phase 1/3: Backend Tests (Go)${NC}"
6968
echo -e "${CYAN}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}"
7069
echo ""
7170

72-
cd "$BACKEND_DIR"
71+
cd "$PROJECT_ROOT"
7372

7473
echo -e "${YELLOW}📦 Running go fmt check...${NC}"
7574
if [ "$(gofmt -s -l . | wc -l)" -gt 0 ]; then
@@ -145,20 +144,20 @@ else
145144
# Run migrations
146145
echo -e "${YELLOW}📝 Running database migrations...${NC}"
147146
export ACKIFY_DB_DSN="postgres://postgres:testpassword@localhost:5432/ackify_test?sslmode=disable"
148-
cd "$BACKEND_DIR"
149-
if go run ./cmd/migrate/main.go -migrations-path file://migrations up; then
147+
cd "$PROJECT_ROOT"
148+
if go run ./backend/cmd/migrate/main.go -migrations-path file://backend/migrations up; then
150149
echo -e "${GREEN}✓ Migrations applied${NC}"
151150

152-
# Run integration tests (already in $BACKEND_DIR)
151+
# Run integration tests
153152
export INTEGRATION_TESTS=1
154-
if go test -v -race -tags=integration -p 1 -count=1 ./internal/infrastructure/database/... ./internal/presentation/api/admin; then
153+
if go test -v -race -tags=integration -p 1 -count=1 ./backend/internal/infrastructure/database/... ./backend/internal/presentation/api/admin; then
155154
echo -e "${GREEN}✓ Integration tests passed${NC}"
156155

157156
# Generate integration coverage
158157
echo -e "${YELLOW}📊 Generating integration test coverage...${NC}"
159158
go test -race -tags=integration -p 1 -count=1 \
160159
-covermode=atomic -coverprofile="$COVERAGE_DIR/backend-integration.out" \
161-
./internal/infrastructure/database/... ./internal/presentation/api/admin 2>&1 | grep -v "no test files" || true
160+
./backend/internal/infrastructure/database/... ./backend/internal/presentation/api/admin 2>&1 | grep -v "no test files" || true
162161
echo -e "${GREEN}✓ Integration coverage generated${NC}"
163162
else
164163
echo -e "${RED}❌ Integration tests failed${NC}"
Lines changed: 167 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,167 @@
1+
// SPDX-License-Identifier: AGPL-3.0-or-later
2+
/// <reference types="cypress" />
3+
4+
describe('Test 11: Webhooks CRUD Operations', () => {
5+
const webhookTitle = 'Test Webhook ' + Date.now()
6+
const webhookUrl = 'https://webhook.site/' + Date.now()
7+
8+
beforeEach(() => {
9+
cy.clearCookies()
10+
})
11+
12+
it('should allow admin to access webhooks page', () => {
13+
// Step 1: Login as admin
14+
cy.loginAsAdmin()
15+
16+
// Step 2: Navigate to webhooks page
17+
cy.visit('/admin/webhooks')
18+
cy.url({ timeout: 10000 }).should('include', '/admin/webhooks')
19+
20+
// Step 3: Verify page elements
21+
cy.contains('Webhooks', { timeout: 10000 }).should('be.visible')
22+
cy.contains('button', 'New webhook').should('be.visible')
23+
})
24+
25+
it('should create a new webhook', () => {
26+
// Step 1: Login and navigate to webhooks
27+
cy.loginAsAdmin()
28+
cy.visit('/admin/webhooks')
29+
30+
// Step 2: Click create webhook button
31+
cy.contains('button', 'New webhook').click()
32+
33+
// Step 3: Should navigate to new webhook form
34+
cy.url({ timeout: 10000 }).should('include', '/admin/webhooks/new')
35+
cy.contains('New webhook', { timeout: 10000 }).should('be.visible')
36+
37+
// Step 4: Fill in webhook form (using Input components with v-model)
38+
cy.get('input[type="text"]').first().type(webhookTitle)
39+
cy.get('input[type="url"]').type(webhookUrl)
40+
cy.get('input[type="text"]').eq(1).type('test_webhook_secret_123')
41+
42+
// Step 5: Select event types (checkboxes)
43+
cy.get('input[type="checkbox"]').then(($checkboxes) => {
44+
// Select first 3 event types
45+
cy.wrap($checkboxes[0]).check()
46+
cy.wrap($checkboxes[1]).check()
47+
cy.wrap($checkboxes[2]).check()
48+
})
49+
50+
// Step 6: Add description (optional)
51+
cy.get('textarea').type('E2E test webhook for document and signature events')
52+
53+
// Step 7: Submit form
54+
cy.get('button[type="submit"]').click()
55+
56+
// Step 8: Should redirect back to webhooks list
57+
cy.url({ timeout: 10000 }).should('eq', Cypress.config('baseUrl') + '/admin/webhooks')
58+
59+
// Step 9: Verify webhook appears in list
60+
cy.contains(webhookTitle, { timeout: 10000 }).should('be.visible')
61+
cy.contains(webhookUrl).should('be.visible')
62+
63+
// Step 10: Verify webhook is active (check for "Active" status)
64+
cy.contains('table tbody tr', webhookTitle).within(() => {
65+
cy.contains('Active').should('be.visible')
66+
})
67+
})
68+
69+
it('should edit webhook', () => {
70+
// Step 1: Login and navigate to webhooks
71+
cy.loginAsAdmin()
72+
cy.visit('/admin/webhooks')
73+
74+
// Step 2: Click Edit button on webhook row
75+
cy.contains('table tbody tr', webhookTitle, { timeout: 10000 }).within(() => {
76+
cy.contains('button', 'Edit').click()
77+
})
78+
79+
// Step 3: Should navigate to webhook edit page
80+
cy.url({ timeout: 10000 }).should('include', '/admin/webhooks/')
81+
cy.contains('Edit webhook', { timeout: 10000 }).should('be.visible')
82+
83+
// Step 4: Verify webhook data is loaded
84+
cy.get('input[type="text"]').first().should('have.value', webhookTitle)
85+
cy.get('input[type="url"]').should('have.value', webhookUrl)
86+
87+
// Step 5: Update webhook title
88+
const updatedTitle = webhookTitle + ' (Updated)'
89+
cy.get('input[type="text"]').first().clear().type(updatedTitle)
90+
91+
// Step 6: Update description
92+
cy.get('textarea').clear().type('Updated webhook description')
93+
94+
// Step 7: Submit form
95+
cy.get('button[type="submit"]').click()
96+
97+
// Step 8: Should redirect back to webhooks list
98+
cy.url({ timeout: 10000 }).should('eq', Cypress.config('baseUrl') + '/admin/webhooks')
99+
100+
// Step 9: Verify updated title appears in list
101+
cy.contains(updatedTitle, { timeout: 10000 }).should('be.visible')
102+
})
103+
104+
it('should disable webhook', () => {
105+
// Step 1: Login and navigate to webhooks
106+
cy.loginAsAdmin()
107+
cy.visit('/admin/webhooks')
108+
109+
// Step 2: Find webhook row and disable it
110+
const updatedTitle = webhookTitle + ' (Updated)'
111+
cy.contains('table tbody tr', updatedTitle, { timeout: 10000 }).within(() => {
112+
cy.contains('button', 'Disable').click()
113+
})
114+
115+
// Step 3: Verify webhook is now inactive
116+
cy.contains('table tbody tr', updatedTitle, { timeout: 10000 }).within(() => {
117+
cy.contains('Inactive').should('be.visible')
118+
})
119+
})
120+
121+
it('should enable webhook', () => {
122+
// Step 1: Login and navigate to webhooks
123+
cy.loginAsAdmin()
124+
cy.visit('/admin/webhooks')
125+
126+
// Step 2: Find webhook row and enable it
127+
const updatedTitle = webhookTitle + ' (Updated)'
128+
cy.contains('table tbody tr', updatedTitle, { timeout: 10000 }).within(() => {
129+
cy.contains('button', 'Enable').click()
130+
})
131+
132+
// Step 3: Verify webhook is now active
133+
cy.contains('table tbody tr', updatedTitle, { timeout: 10000 }).within(() => {
134+
cy.contains('Active').should('be.visible')
135+
})
136+
})
137+
138+
it('should delete webhook from list', () => {
139+
// Step 1: Login and navigate to webhooks
140+
cy.loginAsAdmin()
141+
cy.visit('/admin/webhooks')
142+
143+
// Step 2: Find webhook row and click delete button
144+
const updatedTitle = webhookTitle + ' (Updated)'
145+
cy.contains('table tbody tr', updatedTitle, { timeout: 10000 }).within(() => {
146+
cy.contains('button', 'Delete').click()
147+
})
148+
149+
// Step 3: Confirm deletion in browser confirm dialog (handled by Cypress automatically)
150+
// The webhook list page uses native confirm() dialog
151+
152+
// Step 4: Verify webhook is deleted from list
153+
cy.contains(updatedTitle, { timeout: 10000 }).should('not.exist')
154+
})
155+
156+
it('should prevent non-admin from accessing webhooks', () => {
157+
// Step 1: Login as regular user (not admin)
158+
cy.loginViaMagicLink('user@test.com')
159+
160+
// Step 2: Try to navigate to webhooks page
161+
cy.visit('/admin/webhooks', { failOnStatusCode: false })
162+
163+
// Step 3: Should redirect to home
164+
cy.url({ timeout: 10000 }).should('not.include', '/admin/webhooks')
165+
cy.url().should('match', /\/$/)
166+
})
167+
})

0 commit comments

Comments
 (0)