Fix API CI failures
This commit is contained in:
@@ -157,6 +157,7 @@ jobs:
|
|||||||
env:
|
env:
|
||||||
COMPOSE_FILE: docker-compose.yml:.github/docker-compose.ci.yml
|
COMPOSE_FILE: docker-compose.yml:.github/docker-compose.ci.yml
|
||||||
COMPOSE_PROJECT_NAME: edge-gateway-backend-${{ github.run_id }}-${{ github.run_attempt }}
|
COMPOSE_PROJECT_NAME: edge-gateway-backend-${{ github.run_id }}-${{ github.run_attempt }}
|
||||||
|
COMPOSE_PROFILES: dev
|
||||||
TRAEFIK_WEB_PORT: "18080"
|
TRAEFIK_WEB_PORT: "18080"
|
||||||
TRAEFIK_WEBSECURE_PORT: "18443"
|
TRAEFIK_WEBSECURE_PORT: "18443"
|
||||||
TRAEFIK_WEBSECURE_STAGING_PORT: "18433"
|
TRAEFIK_WEBSECURE_STAGING_PORT: "18433"
|
||||||
@@ -235,11 +236,8 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
node-version: 22
|
node-version: 22
|
||||||
|
|
||||||
- name: Prune stale Docker networks
|
|
||||||
run: docker network prune -f || true
|
|
||||||
|
|
||||||
- name: Boot local stack
|
- 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 php2 php3 php4 php5 caddy
|
run: sh scripts/ci-docker-compose-up.sh traefik redis mysql-debug edge-broker php1 php2 php3 php4 php5 caddy
|
||||||
|
|
||||||
- name: Sync PHP app checkout
|
- name: Sync PHP app checkout
|
||||||
run: >
|
run: >
|
||||||
|
|||||||
@@ -14,3 +14,6 @@
|
|||||||
/.tmp/
|
/.tmp/
|
||||||
/.env.staging
|
/.env.staging
|
||||||
/services/nginx/app/storage/replication-bootstrap.json
|
/services/nginx/app/storage/replication-bootstrap.json
|
||||||
|
/.env_old_2
|
||||||
|
/.openclaw/
|
||||||
|
/services/nginx/app/build/phpstan/
|
||||||
|
|||||||
@@ -0,0 +1,45 @@
|
|||||||
|
#!/usr/bin/env sh
|
||||||
|
set -eu
|
||||||
|
|
||||||
|
if [ "$#" -eq 0 ]; then
|
||||||
|
echo "Usage: $0 <service> [service ...]" >&2
|
||||||
|
exit 2
|
||||||
|
fi
|
||||||
|
|
||||||
|
compose_files="${CI_DOCKER_COMPOSE_FILES:--f docker-compose.yml -f .github/docker-compose.ci.yml}"
|
||||||
|
lock_file="${CI_DOCKER_LOCK_FILE:-/tmp/pleno-api-ci-docker-compose-up.lock}"
|
||||||
|
max_attempts="${CI_DOCKER_UP_RETRIES:-${PHP_CI_DOCKER_RETRIES:-3}}"
|
||||||
|
export COMPOSE_PROFILES="${COMPOSE_PROFILES:-dev}"
|
||||||
|
|
||||||
|
compose_up() {
|
||||||
|
attempt=1
|
||||||
|
while :; do
|
||||||
|
docker network prune -f >/dev/null 2>&1 || true
|
||||||
|
|
||||||
|
if docker compose $compose_files up -d "$@"; then
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
status="$?"
|
||||||
|
docker compose $compose_files down -v --remove-orphans >/dev/null 2>&1 || true
|
||||||
|
|
||||||
|
if [ "$attempt" -ge "$max_attempts" ]; then
|
||||||
|
return "$status"
|
||||||
|
fi
|
||||||
|
|
||||||
|
sleep_seconds=$((attempt * 5))
|
||||||
|
echo "Docker compose up failed with status $status; retrying in ${sleep_seconds}s (attempt $((attempt + 1))/$max_attempts)." >&2
|
||||||
|
sleep "$sleep_seconds"
|
||||||
|
attempt=$((attempt + 1))
|
||||||
|
done
|
||||||
|
}
|
||||||
|
|
||||||
|
if command -v flock >/dev/null 2>&1; then
|
||||||
|
(
|
||||||
|
flock 9
|
||||||
|
compose_up "$@"
|
||||||
|
) 9>"$lock_file"
|
||||||
|
else
|
||||||
|
echo "flock is not available; running Docker compose startup without a host lock." >&2
|
||||||
|
compose_up "$@"
|
||||||
|
fi
|
||||||
@@ -18,6 +18,7 @@ cd "$repo_root"
|
|||||||
compose_files="-f docker-compose.yml -f .github/docker-compose.ci.yml"
|
compose_files="-f docker-compose.yml -f .github/docker-compose.ci.yml"
|
||||||
project_suffix="$(date +%s)-$$"
|
project_suffix="$(date +%s)-$$"
|
||||||
export COMPOSE_PROJECT_NAME="${COMPOSE_PROJECT_NAME:-php-local-${suite}-${project_suffix}}"
|
export COMPOSE_PROJECT_NAME="${COMPOSE_PROJECT_NAME:-php-local-${suite}-${project_suffix}}"
|
||||||
|
export COMPOSE_PROFILES="${COMPOSE_PROFILES:-dev}"
|
||||||
|
|
||||||
log_dir=".tmp/ci-logs/$suite"
|
log_dir=".tmp/ci-logs/$suite"
|
||||||
mkdir -p "$log_dir"
|
mkdir -p "$log_dir"
|
||||||
@@ -84,10 +85,6 @@ composer_install() {
|
|||||||
'cd /var/www/html && composer install --no-interaction --prefer-source --no-progress'
|
'cd /var/www/html && composer install --no-interaction --prefer-source --no-progress'
|
||||||
}
|
}
|
||||||
|
|
||||||
prune_unused_docker_networks() {
|
|
||||||
docker network prune -f >/dev/null 2>&1 || true
|
|
||||||
}
|
|
||||||
|
|
||||||
configure_ci_docker_subnet() {
|
configure_ci_docker_subnet() {
|
||||||
if [ -n "${CI_DOCKER_SUBNET:-}" ]; then
|
if [ -n "${CI_DOCKER_SUBNET:-}" ]; then
|
||||||
return
|
return
|
||||||
@@ -119,8 +116,7 @@ cleanup() {
|
|||||||
trap cleanup EXIT INT TERM
|
trap cleanup EXIT INT TERM
|
||||||
|
|
||||||
configure_ci_docker_subnet
|
configure_ci_docker_subnet
|
||||||
prune_unused_docker_networks
|
sh scripts/ci-docker-compose-up.sh redis mysql-debug php1
|
||||||
retry_command "${PHP_CI_DOCKER_RETRIES:-3}" docker compose $compose_files up -d redis mysql-debug php1
|
|
||||||
|
|
||||||
docker compose $compose_files exec -T php1 sh -lc '
|
docker compose $compose_files exec -T php1 sh -lc '
|
||||||
set -eu
|
set -eu
|
||||||
|
|||||||
@@ -314,7 +314,9 @@ it('creates updates lists and deactivates scoped employees without exposing raw
|
|||||||
|
|
||||||
expect($deactivated->data()['active'] ?? true)->toBeFalse();
|
expect($deactivated->data()['active'] ?? true)->toBeFalse();
|
||||||
$userRow = api_test_runtime()->queryOne('SELECT `password`, `group_id`, `deleted_at` FROM `users` WHERE `id` = ' . $employeeId);
|
$userRow = api_test_runtime()->queryOne('SELECT `password`, `group_id`, `deleted_at` FROM `users` WHERE `id` = ' . $employeeId);
|
||||||
expect($userRow['password'] ?? 'not-null')->toBeNull();
|
expect($userRow)->not->toBeNull();
|
||||||
|
expect(array_key_exists('password', $userRow ?? []))->toBeTrue();
|
||||||
|
expect($userRow['password'])->toBeNull();
|
||||||
expect((int)($userRow['group_id'] ?? -1))->toBe(0);
|
expect((int)($userRow['group_id'] ?? -1))->toBe(0);
|
||||||
expect($userRow['deleted_at'] ?? null)->not->toBeNull();
|
expect($userRow['deleted_at'] ?? null)->not->toBeNull();
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -0,0 +1,41 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
it('services broker transport before replaying queued outbox items', function (): void {
|
||||||
|
$agentSource = (string)file_get_contents(app_path('resources/edge-gateway-agent/agent.php'));
|
||||||
|
$runOffset = strpos($agentSource, 'public function run(): void');
|
||||||
|
expect($runOffset)->not->toBeFalse();
|
||||||
|
|
||||||
|
$loopSource = substr($agentSource, (int)$runOffset, 1800);
|
||||||
|
$configureOffset = strpos($loopSource, '$this->configureBrokerClient();');
|
||||||
|
$pumpOffset = strpos($loopSource, '$this->pumpBrokerTransport();');
|
||||||
|
$flushOffset = strpos($loopSource, '$this->flushOutbox();');
|
||||||
|
|
||||||
|
expect($configureOffset)->not->toBeFalse()
|
||||||
|
->and($pumpOffset)->not->toBeFalse()
|
||||||
|
->and($flushOffset)->not->toBeFalse()
|
||||||
|
->and((int)$configureOffset)->toBeLessThan((int)$pumpOffset)
|
||||||
|
->and((int)$pumpOffset)->toBeLessThan((int)$flushOffset);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('bounds stale outbox replay so it cannot monopolize the gateway loop', function (): void {
|
||||||
|
$agentSource = (string)file_get_contents(app_path('resources/edge-gateway-agent/agent.php'));
|
||||||
|
|
||||||
|
expect($agentSource)->toContain('private const OUTBOX_REPLAY_BATCH_LIMIT = 3;')
|
||||||
|
->and($agentSource)->toContain('private const OUTBOX_REPLAY_TIMEOUT_SECONDS = 3;')
|
||||||
|
->and($agentSource)->toContain('private const OUTBOX_OPERATION_COMPLETE_REPLAY_TIMEOUT_SECONDS = 10;')
|
||||||
|
->and($agentSource)->toContain('private const OUTBOX_REPLAY_FAILURE_COOLDOWN_SECONDS = 15;')
|
||||||
|
->and($agentSource)->toContain('private int $lastOutboxFailureAt = 0;')
|
||||||
|
->and($agentSource)->toContain('$this->stateStore->queuedItems(self::OUTBOX_REPLAY_BATCH_LIMIT)')
|
||||||
|
->and($agentSource)->toContain('private function shouldSkipOutboxReplay(): bool')
|
||||||
|
->and($agentSource)->toContain('$this->lastOutboxFailureAt = time();')
|
||||||
|
->and($agentSource)->toContain('? self::OUTBOX_OPERATION_COMPLETE_REPLAY_TIMEOUT_SECONDS')
|
||||||
|
->and($agentSource)->toContain(': self::OUTBOX_REPLAY_TIMEOUT_SECONDS;');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('uses a short control-plane timeout for self-serve machine signals before queueing', function (): void {
|
||||||
|
$agentSource = (string)file_get_contents(app_path('resources/edge-gateway-agent/agent.php'));
|
||||||
|
|
||||||
|
expect($agentSource)->toContain('private const MACHINE_SIGNAL_TIMEOUT_SECONDS = 3;')
|
||||||
|
->and($agentSource)->toContain('private function sendControlPlaneEvent(string $endpoint, array $payload, string $type, int $timeoutSeconds = 20): bool')
|
||||||
|
->and($agentSource)->toContain("'machine_signal',\n self::MACHINE_SIGNAL_TIMEOUT_SECONDS");
|
||||||
|
});
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
it('wires self-serve machine signals through the broker control plane', function (): void {
|
||||||
|
$routeSource = (string)file_get_contents(app_path('routes/edgeGatewaysRoute.php'));
|
||||||
|
$agentSource = (string)file_get_contents(app_path('resources/edge-gateway-agent/agent.php'));
|
||||||
|
$machineSignalSource = (string)file_get_contents(app_path('modules/selfserve/classes/selfserve_machine_signal.php'));
|
||||||
|
|
||||||
|
expect($routeSource)->toContain("'/edge-agent/internal/gateways/{id}/selfserve/machine-signal'")
|
||||||
|
->and($routeSource)->toContain('private function handleBrokerSelfserveMachineSignal(): void')
|
||||||
|
->and($routeSource)->toContain('recordBrokerEdgeGatewaySignal($gatewayId, $payload)')
|
||||||
|
->and($agentSource)->toContain("'type' => 'MACHINE_SIGNAL'")
|
||||||
|
->and($agentSource)->toContain('#/edge-agent/gateways/\d+/selfserve/machine-signal$#')
|
||||||
|
->and($machineSignalSource)->toContain('public function recordBrokerEdgeGatewaySignal(int $gatewayId, array $payload): array')
|
||||||
|
->and($machineSignalSource)->toContain('private function recordEdgeGatewaySignalForDepartment(int $gatewayId, int $departmentId, array $payload): array');
|
||||||
|
});
|
||||||
Reference in New Issue
Block a user