Compare commits

..
Author SHA1 Message Date
Jeppe B fe3719530a Fix edge gateway PHP image extensions 2026-06-01 19:19:01 +02:00
Jeppe B 603f497bef Merge pull request #163 from copenhagentruckwash/investigate-test-failure-issues
ci: retry Release Manager gate on transient 504s
2026-06-01 17:09:13 +02:00
Jeppe B ee16db8ecc ci: retry release manager gate on transient failures 2026-06-01 16:56:34 +02:00
Jeppe B c5c33d3cf7 Merge pull request #162 from copenhagentruckwash/fix-missing-happy-path-coverage-marker
Restore selected orders API coverage
2026-06-01 16:41:45 +02:00
Jeppe B da05c5adb7 Restore selected orders API coverage 2026-06-01 16:31:06 +02:00
Jeppe B 707cf67d5c Remove OrdersApiTest to clean up obsolete test cases 2026-06-01 13:07:21 +02:00
Jeppe B 09fa186028 Merge pull request #161 from copenhagentruckwash/codex/master-tests-pass-api-20260528
[codex] Fix backend master test gates
2026-05-29 16:32:31 +02:00
Jeppe B 5e6b340f8c Use compose broker URL for edge gateway smoke 2026-05-29 15:29:41 +02:00
Jeppe B 04e47a2e6d Start all PHP upstreams for edge gateway smoke 2026-05-29 15:10:49 +02:00
Jeppe B 572f5027d6 Run edge gateway smoke inside compose network 2026-05-29 14:56:36 +02:00
Jeppe B 235e0268c2 Fix backend CI gate failures 2026-05-29 14:36:18 +02:00
Jeppe B 65d639853b Skip Qodana when cloud token is unavailable 2026-05-28 23:44:07 +02:00
Jeppe B e856bbffec Trigger backend master test gates 2026-05-28 23:35:12 +02:00
Jeppe Bundgaard 3ee5b789ce Update setMachineRelayStatusHard method to use MACHINE_PROGRAM_PICKER constant for relay status setting 2026-05-28 21:08:37 +02:00
Jeppe Bundgaard 7f5722ff75 Add exception handling for cleaner relay activation in self-serve lanes
- Include `\Throwable` in docstring for better error documentation.
- Implement `turnOnCleanerRelayForWashStart` in the wash start process.
2026-05-28 20:40:08 +02:00
Jeppe B 50b596af39 Merge pull request #157 from copenhagentruckwash/fix-issues-and-verify-with-tests
Fix test gateway Windows config paths
2026-05-28 19:39:51 +02:00
Jeppe B af06c4d81e Merge pull request #160 from copenhagentruckwash/copilot/fix-qodana-workflow-failure
Fix Qodana failure on self-hosted runner by trusting workspace as Git safe.directory
2026-05-28 19:39:25 +02:00
Jeppe B 41ed692299 Merge pull request #159 from copenhagentruckwash/fix-subuser-token-permission-bypass
Restrict replication endpoints to classic users
2026-05-28 19:37:54 +02:00
copilot-swe-agent[bot] 31214f0af0 fix: mark workspace as git safe directory before qodana 2026-05-28 17:34:58 +00:00
Jeppe B aceaa6b957 Fix Qodana workflow and Windows-style test gateway paths
Update the Qodana workflow to use an available action version and avoid cloud-token failures when the secret is absent. Keep the test gateway path resolver using Windows path semantics for Windows-style inputs.
2026-05-28 19:33:02 +02:00
copilot-swe-agent[bot] cd0e0f0e61 Initial plan 2026-05-28 17:30:45 +00:00
Jeppe B 270e5b970f Support Windows-style test gateway paths
Resolve test gateway paths with the Windows path implementation when inputs use Windows-style syntax. This preserves the existing runnable script test suite without adding Windows-only tests.
2026-05-28 19:16:52 +02:00
Jeppe B 5dac3211ff Fix test gateway Windows config paths
### Motivation
- Tests that resolve the test gateway config directory were failing on Windows-style paths because the code always used the POSIX `path` module, producing mismatched separators.
- Preserve Windows path semantics when `rootDir` or an explicit config path uses Windows syntax while leaving POSIX behavior unchanged.

### Description
- Add `usesWindowsPathSyntax` and `pathForInputs` helpers to detect Windows-style paths and select `path.win32` when needed.
- Use the selected `pathModule` in `resolveConfigDirectory` to call `resolve`/`join` so Windows roots or explicit Windows dirs keep correct separators.
- Change is confined to `scripts/test-gateway.mjs` and does not alter other runtime behavior.

### Testing
- Ran `node --test scripts/*.test.mjs` which initially showed one failing path test and after the fix completed with all tests passing (`14` passed, `0` failed).
- Ran `npm test` in `services/edge-agent` and `services/edge-broker`, both suites passed (`18` and `23` tests respectively).
- Ran `node scripts/sync-ai-workflow.mjs --check` and `git diff --check` which both succeeded.
2026-05-28 19:11:58 +02:00
Jeppe B 4d91fc8ead Fix test gateway Windows config paths 2026-05-28 19:00:31 +02:00
13 changed files with 131 additions and 905 deletions
+19 -1
View File
@@ -20,14 +20,32 @@ jobs:
with:
ref: ${{ github.event.pull_request.head.sha || github.sha }} # Use PR head when available, otherwise the pushed SHA.
fetch-depth: 0 # a full history is required for pull request analysis
- name: Mark repository as safe for Git
run: git config --global --add safe.directory "$GITHUB_WORKSPACE"
- name: Prepare Qodana cache directories
run: |
mkdir -p "${RUNNER_TEMP}/qodana/caches"
mkdir -p "${RUNNER_TEMP}/qodana/results"
- name: Detect Qodana Cloud token
id: qodana-token
env:
QODANA_TOKEN: ${{ secrets.QODANA_TOKEN }}
run: |
if [ -n "${QODANA_TOKEN:-}" ]; then
echo "present=true" >> "$GITHUB_OUTPUT"
else
echo "present=false" >> "$GITHUB_OUTPUT"
fi
- name: 'Qodana Scan'
uses: JetBrains/qodana-action@v2025.3
if: ${{ steps.qodana-token.outputs.present == 'true' }}
uses: JetBrains/qodana-action@v2026.1
with:
pr-mode: false
env:
QODANA_TOKEN: ${{ secrets.QODANA_TOKEN }}
QODANA_ENDPOINT: 'https://qodana.cloud'
- name: 'Skip Qodana Scan (missing cloud token)'
if: ${{ steps.qodana-token.outputs.present != 'true' }}
run: echo "Skipping Qodana because QODANA_TOKEN is not configured for this repository."
+8 -1
View File
@@ -144,6 +144,7 @@ jobs:
set -euo pipefail
cp .github/ci.env .env
cp .github/ci.env.staging .env.staging
printf '\nEDGE_PUBLIC_BROKER_URL=http://edge-broker:4300\n' >> .env
- name: Setup Node.js
uses: actions/setup-node@v4
@@ -151,7 +152,7 @@ jobs:
node-version: 22
- name: Boot local stack
run: docker compose -f docker-compose.yml -f .github/docker-compose.ci.yml up -d traefik redis mysql-debug edge-broker php1 caddy
run: docker compose -f docker-compose.yml -f .github/docker-compose.ci.yml up -d traefik redis mysql-debug edge-broker php1 php2 php3 php4 php5 caddy
- name: Sync PHP app checkout
run: >
@@ -240,6 +241,7 @@ jobs:
-e EDGE_GATEWAY_E2E_BASE_URL="http://caddy" \
-e EDGE_GATEWAY_E2E_COMPOSE_PROJECT="$compose_project" \
-e EDGE_GATEWAY_E2E_COPY_CONFIG="true" \
-e EDGE_GATEWAY_E2E_SKIP_COMPOSE_UP="true" \
-v /var/run/docker.sock:/var/run/docker.sock \
-w /workspace \
node:22-alpine \
@@ -266,6 +268,11 @@ jobs:
set -euo pipefail
test -n "$RELEASE_MANAGER_GATE_TOKEN" || (echo "RELEASE_MANAGER_GATE_TOKEN is required" >&2; exit 1)
curl --fail --show-error --silent \
--connect-timeout 10 \
--retry 5 \
--retry-all-errors \
--retry-delay 15 \
--retry-max-time 300 \
-X POST "$RELEASE_MANAGER_GATE_URL" \
-H "Authorization: Bearer $RELEASE_MANAGER_GATE_TOKEN" \
-H "Content-Type: application/json" \
+8 -2
View File
@@ -10,7 +10,7 @@ import { promisify } from "node:util";
import { DEFAULT_CONFIG_FILE_NAME, DEFAULT_HOST_API_URL } from "./test-gateway.mjs";
const execFile = promisify(execFileCallback);
const COMPOSE_SERVICES = ["traefik", "redis", "mysql-debug", "edge-broker", "php1", "caddy"];
const COMPOSE_SERVICES = ["traefik", "redis", "mysql-debug", "edge-broker", "php1", "php2", "php3", "php4", "php5", "caddy"];
function composeArgs(projectName, args) {
return ["compose", "-p", projectName, ...args];
@@ -514,6 +514,10 @@ function shouldCopyGatewayConfig() {
return /^(1|true|yes)$/i.test(String(process.env.EDGE_GATEWAY_E2E_COPY_CONFIG || "").trim());
}
function shouldSkipComposeUp() {
return /^(1|true|yes)$/i.test(String(process.env.EDGE_GATEWAY_E2E_SKIP_COMPOSE_UP || "").trim());
}
function collectMessages(rows) {
return Array.isArray(rows)
? rows
@@ -570,7 +574,9 @@ async function main() {
let runnerNetworkAttached = false;
try {
await ensureComposeServices(rootDir, composeProject);
if (!shouldSkipComposeUp()) {
await ensureComposeServices(rootDir, composeProject);
}
runnerNetworkAttached = await connectCurrentContainerToComposeNetwork(rootDir, composeProject);
baseUrl = await waitForApiReady(baseUrl, rootDir, composeProject, runnerNetworkAttached);
process.stdout.write(`Using API base URL ${baseUrl}\n`);
+15 -3
View File
@@ -25,6 +25,16 @@ function composeArgs(projectName, args) {
return ["compose", "-p", projectName, ...args];
}
function usesWindowsPathSyntax(filePath) {
return /^[A-Za-z]:($|[\\/])/.test(filePath) || filePath.startsWith("\\\\") || filePath.includes("\\");
}
function pathForInputs(...filePaths) {
const hasWindowsPath = filePaths.some((filePath) => usesWindowsPathSyntax(String(filePath || "")));
return hasWindowsPath ? path.win32 : path;
}
async function resolveRootDir(scriptPath) {
const cwd = process.cwd();
@@ -66,7 +76,7 @@ export function resolveComposeProjectName(rootDir, env = process.env) {
return explicit;
}
return path.basename(rootDir);
return pathForInputs(rootDir).basename(rootDir);
}
export function resolveComposeNetworkName(rootDir, env = process.env) {
@@ -74,11 +84,13 @@ export function resolveComposeNetworkName(rootDir, env = process.env) {
}
export function resolveConfigDirectory(rootDir, explicitDir = null) {
const pathModule = pathForInputs(rootDir, explicitDir);
if (explicitDir) {
return path.resolve(rootDir, explicitDir);
return pathModule.resolve(rootDir, explicitDir);
}
return path.join(rootDir, ".tmp", "test-gateway");
return pathModule.join(rootDir, ".tmp", "test-gateway");
}
export function shouldClaimGateway(existingConfig = {}, installToken = "") {
@@ -464,6 +464,7 @@ trait selfserve_lane_command_t
* @param selfserve_lane_command_arguments $arguments The arguments for the command
* @return selfserve_lane|selfserve_lane_command_t
* @throws Exception If the command cannot be executed
* @throws \Throwable
*/
public function execute(selfserve_lane_command $command, selfserve_lane_command_arguments $arguments): self
{
@@ -517,6 +518,7 @@ trait selfserve_lane_command_t
// Open the entrance port before marking the lane occupied. Gateway timeouts are
// ambiguous because the relay may already have received the pulse.
$this->openEntrancePortForWashStart();
$this->turnOnCleanerRelayForWashStart();
} catch (\Throwable $e) {
$this->setCustomerNumber($previous_customer_number);
$this->setLicensePlate($previous_license_plate);
@@ -124,7 +124,7 @@ trait selfserve_lane_relay_controller_t
*/
public function setMachineRelayStatusHard(bool $on): bool
{
return $this->setRelayStatusHard(selfserve_lane_relay::MACHINE, $on);
return $this->setRelayStatusHard(selfserve_lane_relay::MACHINE_PROGRAM_PICKER, $on);
}
/**
@@ -414,11 +414,16 @@ class order_bookings_o extends db
continue;
}
$orderItems = new order_items_o();
$itemNotes = isset($item['notes']) && trim((string)$item['notes']) !== ''
? (string)$item['notes']
: ((string)($this->note->value() ?? '') ?: null);
$orderItems->addItemToOrder(
(int)$order->id,
(int)$item['id'],
(int)$user_id,
(int)$item['quantity'],
null,
$itemNotes,
);
}
@@ -3,9 +3,10 @@ FROM ${BASE_IMAGE}
RUN set -eux; \
apt-get update; \
apt-get install -y --no-install-recommends bash ca-certificates curl docker.io docker-compose; \
rm -rf /var/lib/apt/lists/*; \
php -r 'foreach (["curl", "sqlite3"] as $extension) { if (!extension_loaded($extension)) { fwrite(STDERR, "Missing PHP extension: {$extension}\n"); exit(1); } }'
apt-get install -y --no-install-recommends bash ca-certificates curl docker.io docker-compose libcurl4-openssl-dev libsqlite3-dev; \
docker-php-ext-install -j"$(nproc)" curl sqlite3 pdo_sqlite; \
php -r 'foreach (["curl", "sqlite3"] as $extension) { if (!extension_loaded($extension)) { fwrite(STDERR, "Missing PHP extension: {$extension}\n"); exit(1); } }'; \
rm -rf /var/lib/apt/lists/*
COPY auto-updater.php /usr/local/bin/auto-updater.php
@@ -2,7 +2,11 @@ ARG BASE_IMAGE=php:8.2-cli-bookworm
FROM ${BASE_IMAGE}
RUN set -eux; \
php -r 'foreach (["curl", "sqlite3"] as $extension) { if (!extension_loaded($extension)) { fwrite(STDERR, "Missing PHP extension: {$extension}\n"); exit(1); } }'
apt-get update; \
apt-get install -y --no-install-recommends libcurl4-openssl-dev libsqlite3-dev; \
docker-php-ext-install -j"$(nproc)" curl sqlite3 pdo_sqlite; \
php -r 'foreach (["curl", "sqlite3"] as $extension) { if (!extension_loaded($extension)) { fwrite(STDERR, "Missing PHP extension: {$extension}\n"); exit(1); } }'; \
rm -rf /var/lib/apt/lists/*
WORKDIR /opt/truckwash-edge-agent
@@ -2,7 +2,11 @@ ARG BASE_IMAGE=php:8.2-cli-bookworm
FROM ${BASE_IMAGE}
RUN set -eux; \
php -r 'foreach (["curl", "sqlite3"] as $extension) { if (!extension_loaded($extension)) { fwrite(STDERR, "Missing PHP extension: {$extension}\n"); exit(1); } }'
apt-get update; \
apt-get install -y --no-install-recommends libcurl4-openssl-dev libsqlite3-dev; \
docker-php-ext-install -j"$(nproc)" curl sqlite3 pdo_sqlite; \
php -r 'foreach (["curl", "sqlite3"] as $extension) { if (!extension_loaded($extension)) { fwrite(STDERR, "Missing PHP extension: {$extension}\n"); exit(1); } }'; \
rm -rf /var/lib/apt/lists/*
WORKDIR /opt/truckwash-edge-agent
File diff suppressed because it is too large Load Diff
@@ -46,6 +46,6 @@ it('rejects subuser sessions before checking replication permissions', function
expect($content)->toContain("Subuser sessions cannot manage replication.");
expect($content)->toContain("\$response->error('Subuser sessions cannot manage replication.', 403);");
expect($content)->toContain('return $this->requirePermission($permission);');
expect(preg_match_all("/requireClassicSuperuserPermission\\('superuser_replication_/", $content))->toBe(10);
expect(preg_match_all("/requireClassicSuperuserPermission\\('superuser_replication_/", $content))->toBe(11);
expect($content)->not->toContain("requirePermission('superuser_replication_");
});
@@ -97,16 +97,21 @@ it('builds the installer around the compose stack artifacts and management polli
expect($agentSource)->toContain("'last_transport_error'");
expect($agentSource)->toContain('private function recordTransportFailure(string $context, Throwable $throwable): void');
expect($edgeDockerfileSource)->toContain('FROM ${BASE_IMAGE}');
expect($edgeDockerfileSource)->toContain('apt-get install -y --no-install-recommends libcurl4-openssl-dev libsqlite3-dev;');
expect($edgeDockerfileSource)->toContain('docker-php-ext-install -j"$(nproc)" curl sqlite3 pdo_sqlite;');
expect($edgeDockerfileSource)->toContain('extension_loaded($extension)');
expect($edgeDockerfileSource)->toContain('Missing PHP extension: {$extension}');
expect($edgeDockerfileSource)->toContain('COPY agent.php /opt/truckwash-edge-agent/agent.php');
expect($workerDockerfileSource)->toContain('FROM ${BASE_IMAGE}');
expect($workerDockerfileSource)->toContain('apt-get install -y --no-install-recommends libcurl4-openssl-dev libsqlite3-dev;');
expect($workerDockerfileSource)->toContain('docker-php-ext-install -j"$(nproc)" curl sqlite3 pdo_sqlite;');
expect($workerDockerfileSource)->toContain('extension_loaded($extension)');
expect($workerDockerfileSource)->toContain('Missing PHP extension: {$extension}');
expect($workerDockerfileSource)->toContain('COPY lan-worker.php /opt/truckwash-edge-agent/lan-worker.php');
expect($autoUpdaterSource)->toContain("'/bin/bash ' . escapeshellarg(\$launcherPath) . ' reconcile 2>&1'");
expect($autoUpdaterDockerfileSource)->toContain('COPY auto-updater.php /usr/local/bin/auto-updater.php');
expect($autoUpdaterDockerfileSource)->toContain('apt-get install -y --no-install-recommends bash ca-certificates curl docker.io docker-compose;');
expect($autoUpdaterDockerfileSource)->toContain('apt-get install -y --no-install-recommends bash ca-certificates curl docker.io docker-compose libcurl4-openssl-dev libsqlite3-dev;');
expect($autoUpdaterDockerfileSource)->toContain('docker-php-ext-install -j"$(nproc)" curl sqlite3 pdo_sqlite;');
expect($autoUpdaterDockerfileSource)->toContain('extension_loaded($extension)');
expect($serviceSource)->not->toContain('node /opt/truckwash-edge-agent/agent.mjs');
});