Skip to content

Commit d129887

Browse files
authored
add workflow file and test to check backwards compatibility with db migrations (#1706)
* add workflow file and test to check backwards compatibility with db migrations * change run setup of k6 * try new migration and another checkout for old commit * fix paths and use working directory * make workflow run only on migration changes * test workflow filtering * add faulty migration * revert faulty migration * test with valid migration * revert valid migration
1 parent fd2e95d commit d129887

File tree

3 files changed

+617
-0
lines changed

3 files changed

+617
-0
lines changed
Lines changed: 295 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,295 @@
1+
name: Verify Database Migration Compatibility
2+
3+
on:
4+
workflow_call:
5+
outputs:
6+
migration-compatible:
7+
description: "Whether old code is compatible with new database"
8+
value: ${{ jobs.test-migration-compatibility.outputs.compatible }}
9+
workflow_dispatch:
10+
inputs:
11+
old_commit:
12+
description: 'Old commit SHA to test'
13+
required: true
14+
type: string
15+
new_commit:
16+
description: 'New commit SHA to test'
17+
required: true
18+
type: string
19+
pull_request:
20+
branches: [main]
21+
paths:
22+
- 'src/Altinn.Correspondence.Persistence/**'
23+
24+
25+
jobs:
26+
determine-commits:
27+
name: Determine commits to test
28+
runs-on: ubuntu-latest
29+
outputs:
30+
old_commit: ${{ steps.set-commits.outputs.old_commit }}
31+
new_commit: ${{ steps.set-commits.outputs.new_commit }}
32+
steps:
33+
- name: Determine commits to test
34+
id: set-commits
35+
run: |
36+
if [ "${{ github.event_name }}" == "workflow_dispatch" ]; then
37+
# Manual trigger with specified commits
38+
echo "old_commit=${{ inputs.old_commit }}" >> $GITHUB_OUTPUT
39+
echo "new_commit=${{ inputs.new_commit }}" >> $GITHUB_OUTPUT
40+
elif [ "${{ github.event_name }}" == "workflow_call" ]; then
41+
# Called from another workflow - use current commit and previous
42+
NEW_COMMIT="${{ github.sha }}"
43+
OLD_COMMIT=${{ github.event.repository.default_branch }}
44+
echo "old_commit=$OLD_COMMIT" >> $GITHUB_OUTPUT
45+
echo "new_commit=$NEW_COMMIT" >> $GITHUB_OUTPUT
46+
else
47+
NEW_COMMIT="${{ github.event.pull_request.head.sha || github.sha }}"
48+
OLD_COMMIT="${{ github.event.repository.default_branch }}"
49+
echo "old_commit=$OLD_COMMIT" >> $GITHUB_OUTPUT
50+
echo "new_commit=$NEW_COMMIT" >> $GITHUB_OUTPUT
51+
fi
52+
53+
test-migration-compatibility:
54+
environment: use-case-test
55+
name: Test old code with new database
56+
runs-on: ubuntu-latest
57+
needs: determine-commits
58+
outputs:
59+
compatible: ${{ steps.test-result.outputs.compatible }}
60+
61+
steps:
62+
- name: Checkout new commit to save test files
63+
uses: actions/checkout@v6
64+
with:
65+
ref: ${{ needs.determine-commits.outputs.new_commit }}
66+
path: new-commit
67+
68+
- name: Save test file from new commit
69+
run: |
70+
mkdir -p /tmp/test-scripts/helpers
71+
cp new-commit/Test/Altinn.Correspondence.Tests/verify_db_migration.js /tmp/test-scripts/ || true
72+
cp new-commit/Test/Altinn.Correspondence.UseCaseTests/helpers/correspondencePayloadBuilder.js /tmp/test-scripts/helpers/ || true
73+
74+
- name: Checkout repository at old commit
75+
uses: actions/checkout@v6
76+
with:
77+
ref: ${{ needs.determine-commits.outputs.old_commit }}
78+
path: old-commit
79+
80+
- name: Restore test file for first run
81+
run: |
82+
cp /tmp/test-scripts/verify_db_migration.js old-commit/Test/Altinn.Correspondence.Tests/ || true
83+
cp /tmp/test-scripts/helpers/correspondencePayloadBuilder.js old-commit/Test/Altinn.Correspondence.UseCaseTests/helpers/ || true
84+
85+
- name: Setup .NET
86+
uses: actions/setup-dotnet@v5
87+
with:
88+
dotnet-version: |
89+
10.0.x
90+
91+
- name: Setup k6
92+
uses: grafana/setup-k6-action@v1
93+
94+
# ============================================
95+
# STEP 1: Start environment with OLD code
96+
# ============================================
97+
- name: Start docker compose with old version
98+
working-directory: old-commit
99+
run: |
100+
docker compose up -d
101+
102+
- name: Build old version
103+
working-directory: old-commit
104+
run: |
105+
echo "Building old version..."
106+
dotnet restore
107+
dotnet build --no-restore --configuration Release
108+
109+
- name: Wait for docker compose services to be ready
110+
run: |
111+
echo "Waiting for services to be healthy..."
112+
timeout 5m bash -c 'while ! docker ps | grep "(healthy)"; do sleep 1; done'
113+
echo "Services are ready"
114+
115+
- name: Install EF Core CLI
116+
run: dotnet tool install --global dotnet-ef --version 10.0.*
117+
shell: bash
118+
119+
120+
- name: Run migrations for old version
121+
working-directory: old-commit
122+
run: |
123+
echo "Running migrations from old commit..."
124+
dotnet ef database update --no-build --configuration Release \
125+
--project ./src/Altinn.Correspondence.Persistence \
126+
--startup-project ./src/Altinn.Correspondence.API
127+
128+
- name: Start old version API
129+
working-directory: old-commit
130+
run: |
131+
echo "Starting API with old code..."
132+
nohup dotnet run --no-build --configuration Release \
133+
--project ./src/Altinn.Correspondence.API/Altinn.Correspondence.API.csproj \
134+
</dev/null >/dev/null 2>&1 &
135+
# Wait for API to be ready
136+
echo "Waiting for API to be ready..."
137+
for i in {1..30}; do
138+
if curl -s http://localhost:5096/health > /dev/null 2>&1; then
139+
echo "API is ready!"
140+
break
141+
fi
142+
if [ $i -eq 30 ]; then
143+
echo "API failed to start"
144+
exit 1
145+
fi
146+
sleep 2
147+
done
148+
149+
# ============================================
150+
# STEP 2: Test old code with old database
151+
# ============================================
152+
- name: Create directory to save created IDs
153+
working-directory: old-commit
154+
run: |
155+
mkdir -p ./test-results
156+
157+
- name: Test old code with old database (baseline)
158+
working-directory: old-commit
159+
env:
160+
base_url: http://localhost:5096
161+
mp_client_id: ${{ secrets.BRUKSMONSTER_MASKINPORTEN_CLIENT_ID }}
162+
mp_kid: ${{ secrets.BRUKSMONSTER_MASKINPORTEN_KID }}
163+
mp_client_pem: ${{ secrets.BRUKSMONSTER_MASKINPORTEN_PEM }}
164+
run: |
165+
k6 run Test/Altinn.Correspondence.Tests/verify_db_migration.js --console-output="./test-results/k6_ids.txt"
166+
continue-on-error: false
167+
168+
- name: Temporarily persist created IDs
169+
working-directory: old-commit
170+
run: |
171+
mkdir -p /tmp/test-results
172+
cp ./test-results/k6_ids.txt /tmp/test-results/k6_ids.txt || true
173+
174+
# ============================================
175+
# STEP 3: Apply new migrations
176+
# ============================================
177+
- name: Stop old API
178+
run: |
179+
echo "Stopping old API..."
180+
pkill -f "dotnet.*Altinn.Correspondence.API" || true
181+
sleep 2
182+
183+
- name: Checkout repository at new commit
184+
uses: actions/checkout@v6
185+
with:
186+
ref: ${{ needs.determine-commits.outputs.new_commit }}
187+
path: new-commit-for-migrations
188+
189+
- name: Build new version
190+
working-directory: new-commit-for-migrations
191+
run: |
192+
echo "Building new version to get latest migrations..."
193+
dotnet restore
194+
dotnet build --no-restore --configuration Release
195+
196+
- name: Apply new migrations
197+
working-directory: new-commit-for-migrations
198+
run: |
199+
echo "Applying migrations from new commit..."
200+
dotnet ef database update --no-build --configuration Release \
201+
--project ./src/Altinn.Correspondence.Persistence \
202+
--startup-project ./src/Altinn.Correspondence.API
203+
echo "Migrations applied successfully"
204+
205+
# ============================================
206+
# STEP 4: Test old code with new database
207+
# ============================================
208+
- name: Checkout repository at old commit
209+
uses: actions/checkout@v6
210+
with:
211+
ref: ${{ needs.determine-commits.outputs.old_commit }}
212+
path: old-commit-second
213+
214+
- name: Restore test file for second run
215+
run: |
216+
cp /tmp/test-scripts/verify_db_migration.js old-commit-second/Test/Altinn.Correspondence.Tests/ || true
217+
cp /tmp/test-scripts/helpers/correspondencePayloadBuilder.js old-commit-second/Test/Altinn.Correspondence.UseCaseTests/helpers/ || true
218+
219+
- name: Build old version (second time)
220+
working-directory: old-commit-second
221+
run: |
222+
echo "Building old version for second run..."
223+
dotnet restore
224+
dotnet build --no-restore --configuration Release
225+
226+
- name: Restart old version API (with new database)
227+
working-directory: old-commit-second
228+
run: |
229+
echo "Restarting API with old code (but new database)..."
230+
nohup dotnet run --no-build --configuration Release \
231+
--project ./src/Altinn.Correspondence.API/Altinn.Correspondence.API.csproj \
232+
</dev/null >/dev/null 2>&1 &
233+
234+
# Wait for API to be ready
235+
echo "Waiting for API to be ready..."
236+
for i in {1..30}; do
237+
if curl -s http://localhost:5096/health > /dev/null 2>&1; then
238+
echo "API is ready!"
239+
break
240+
fi
241+
if [ $i -eq 30 ]; then
242+
echo "API failed to start after migration"
243+
exit 1
244+
fi
245+
sleep 2
246+
done
247+
248+
249+
- name: Restore test results
250+
working-directory: old-commit-second
251+
run: |
252+
mkdir -p ./Test/Altinn.Correspondence.Tests/test-results
253+
cp /tmp/test-results/k6_ids.txt ./Test/Altinn.Correspondence.Tests/test-results/k6_ids_input.txt || true
254+
255+
- name: Test old code with new database
256+
working-directory: old-commit-second
257+
env:
258+
base_url: http://localhost:5096
259+
mp_client_id: ${{ secrets.BRUKSMONSTER_MASKINPORTEN_CLIENT_ID }}
260+
mp_kid: ${{ secrets.BRUKSMONSTER_MASKINPORTEN_KID }}
261+
mp_client_pem: ${{ secrets.BRUKSMONSTER_MASKINPORTEN_PEM }}
262+
results_file_path: ./test-results/k6_ids_input.txt
263+
run: |
264+
k6 run Test/Altinn.Correspondence.Tests/verify_db_migration.js
265+
continue-on-error: false
266+
267+
# ============================================
268+
# Results and cleanup
269+
# ============================================
270+
- name: Set test result
271+
id: test-result
272+
if: success()
273+
run: |
274+
echo "compatible=true" >> $GITHUB_OUTPUT
275+
echo "Migration compatibility verified successfully!"
276+
echo "Old code is compatible with new database schema."
277+
278+
report-results:
279+
name: Report test results
280+
runs-on: ubuntu-latest
281+
needs: test-migration-compatibility
282+
if: always()
283+
steps:
284+
- name: Report success
285+
if: needs.test-migration-compatibility.outputs.compatible == 'true'
286+
run: |
287+
echo "Database migration compatibility verified"
288+
echo "Old code successfully works with new database schema"
289+
echo "Safe to proceed with deployment"
290+
291+
- name: Report failure
292+
if: needs.test-migration-compatibility.outputs.compatible != 'true'
293+
run: |
294+
echo "Database migration compatibility test failed"
295+
exit 1

0 commit comments

Comments
 (0)