Skip to content

Commit 93a7666

Browse files
committed
re-add e2e
1 parent e3c9e10 commit 93a7666

File tree

5 files changed

+56
-163
lines changed

5 files changed

+56
-163
lines changed

.github/workflows/tests.yaml

Lines changed: 37 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -59,43 +59,40 @@ jobs:
5959
project-token: ${{ secrets.CODACY_PROJECT_TOKEN }}
6060
coverage-reports: clover.xml
6161

62-
# e2e:
63-
# runs-on: ubuntu-latest
64-
# name: E2E Tests
65-
#
66-
# steps:
67-
# - uses: actions/checkout@v4
68-
#
69-
# - name: Build and start services
70-
# run: docker compose up -d --build php redis
71-
#
72-
# - name: Install PHP dependencies
73-
# run: docker compose exec -T php composer install --no-interaction --prefer-dist --ignore-platform-req=ext-zookeeper --ignore-platform-req=ext-memcached
74-
#
75-
# - name: Start worker
76-
# run: docker compose up -d worker
77-
#
78-
# - name: Wait for services to be ready
79-
# run: |
80-
# echo "Waiting for services..."
81-
# sleep 5
82-
# docker compose ps
83-
#
84-
# - name: Run e2e tests
85-
# run: docker compose run --rm e2e
86-
#
87-
# - name: Show worker logs on failure
88-
# if: failure()
89-
# run: docker compose logs worker
90-
#
91-
# - name: Upload test artifacts
92-
# if: failure()
93-
# uses: actions/upload-artifact@v4
94-
# with:
95-
# name: playwright-report
96-
# path: e2e/test-results/
97-
# retention-days: 7
98-
#
99-
# - name: Stop services
100-
# if: always()
101-
# run: docker compose down
62+
e2e:
63+
runs-on: ubuntu-latest
64+
name: E2E Tests
65+
66+
steps:
67+
- uses: actions/checkout@v4
68+
69+
- name: Build and start services
70+
run: docker compose up -d --build php redis
71+
72+
- name: Install PHP dependencies
73+
run: docker compose exec -T php composer install --no-interaction --prefer-dist --ignore-platform-req=ext-zookeeper --ignore-platform-req=ext-memcached
74+
75+
- name: Wait for services to be ready
76+
run: |
77+
echo "Waiting for services..."
78+
sleep 5
79+
docker compose ps
80+
81+
- name: Run e2e tests
82+
run: docker compose run --rm e2e
83+
84+
- name: Show worker logs on failure
85+
if: failure()
86+
run: docker compose logs worker
87+
88+
- name: Upload test artifacts
89+
if: failure()
90+
uses: actions/upload-artifact@v4
91+
with:
92+
name: playwright-report
93+
path: e2e/test-results/
94+
retention-days: 7
95+
96+
- name: Stop services
97+
if: always()
98+
run: docker compose down

e2e/tests/01-lock.spec.ts

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -24,10 +24,9 @@ test.describe.serial('01-global-lock - Double-Click Protection', () => {
2424

2525
// Should show submitting, then running
2626
await expect(page.locator('#status')).toContainText('Processing');
27-
await expect(page.locator('#status')).toHaveClass(/wait/);
2827

2928
// Wait for completion (5s work + buffer)
30-
await expect(page.locator('#status')).toContainText('Done', {timeout: 4000});
29+
await expect(page.locator('#status')).toContainText('Done', {timeout: 5000});
3130
await expect(page.locator('#status')).toHaveClass(/ok/);
3231
});
3332

@@ -55,7 +54,7 @@ test.describe.serial('01-global-lock - Double-Click Protection', () => {
5554
// First user starts action
5655
await page1.goto('/global-lock/');
5756
await page1.locator('#go').click();
58-
await expect(page1.locator('#status')).toContainText('Submitting');
57+
await expect(page1.locator('#status')).toContainText('Processing');
5958

6059
// Second user tries to start - should be instantly blocked
6160
await page2.goto('/global-lock/');
@@ -68,7 +67,7 @@ test.describe.serial('01-global-lock - Double-Click Protection', () => {
6867

6968
// Second user can now start
7069
await page2.locator('#go').click();
71-
await expect(page2.locator('#status')).toContainText('Submitting');
70+
await expect(page2.locator('#status')).toContainText('Processing');
7271
} finally {
7372
await context1.close();
7473
await context2.close();

examples/src/GlobalLock/GlobalLockService.php

Lines changed: 11 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -5,18 +5,12 @@
55
namespace App\GlobalLock;
66

77
use App\Factory\AirlockFactory;
8-
use App\Infrastructure\JobQueue;
9-
use App\Infrastructure\StatusStore;
108
use Clegginabox\Airlock\EntryResult;
9+
use Clegginabox\Airlock\Seal\SealToken;
1110

1211
final readonly class GlobalLockService
1312
{
14-
public function __construct(
15-
private AirlockFactory $airlockFactory,
16-
private JobQueue $jobs,
17-
private StatusStore $statusStore
18-
) {
19-
}
13+
public function __construct(private AirlockFactory $airlockFactory) {}
2014

2115
public function start(string $clientId, int $durationSeconds = 10): EntryResult
2216
{
@@ -29,35 +23,18 @@ public function start(string $clientId, int $durationSeconds = 10): EntryResult
2923

3024
$result = $airlock->enter($clientId);
3125

32-
if (!$result->isAdmitted()) {
33-
return $result;
34-
}
26+
if ($result->isAdmitted()) {
27+
$token = $result->getToken();
28+
assert($token instanceof SealToken);
3529

36-
$token = $result->getToken();
37-
if ($token === null) {
38-
return EntryResult::queued(-1, '');
30+
try {
31+
// Do a long running task here
32+
sleep($durationSeconds);
33+
} finally {
34+
$airlock->release($token);
35+
}
3936
}
4037

41-
$this->jobs->enqueue(GlobalLock::NAME->value, [
42-
'clientId' => $clientId,
43-
'durationSeconds' => $durationSeconds,
44-
'serializedKey' => (string) $token,
45-
]);
46-
4738
return $result;
4839
}
49-
50-
public function status(string $clientId): array
51-
{
52-
$status = $this->statusStore->get(GlobalLock::NAME->value, $clientId);
53-
54-
if ($status !== null) {
55-
return $status;
56-
}
57-
58-
return [
59-
'state' => 'pending',
60-
'message' => 'Waiting for worker...',
61-
];
62-
}
6340
}

examples/src/GlobalLock/Handler.php

Lines changed: 0 additions & 57 deletions
This file was deleted.

examples/src/GlobalLock/resources/script.js

Lines changed: 5 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1,32 +1,8 @@
11
const status = document.getElementById('status');
22
const button = document.getElementById('go');
33

4-
async function pollStatus(clientId) {
5-
const url = `./global-lock/status?clientId=${encodeURIComponent(clientId)}`;
6-
7-
while (true) {
8-
const res = await fetch(url);
9-
const data = await res.json();
10-
11-
status.textContent = data.message || data.state;
12-
status.className = 'status';
13-
14-
if (data.state === 'running') {
15-
status.classList.add('wait');
16-
} else if (data.state === 'done') {
17-
status.classList.add('ok');
18-
return;
19-
} else if (data.state === 'blocked') {
20-
status.classList.add('error');
21-
return;
22-
}
23-
24-
await new Promise(r => setTimeout(r, 1000));
25-
}
26-
}
27-
284
button.onclick = async () => {
29-
status.textContent = 'Submitting...';
5+
status.textContent = 'Processing...';
306
status.className = 'status';
317

328
try {
@@ -36,10 +12,11 @@ button.onclick = async () => {
3612
if (!res.ok || !data.ok) {
3713
status.textContent = data.error || 'Failed to start';
3814
status.classList.add('error');
39-
return;
15+
} else {
16+
status.textContent = 'Done!';
17+
status.classList.remove('error');
18+
status.classList.add('ok');
4019
}
41-
42-
await pollStatus(data.clientId);
4320
} catch (err) {
4421
status.textContent = 'Error: ' + err.message;
4522
status.classList.add('error');

0 commit comments

Comments
 (0)