Skip to content

Commit ece0a61

Browse files
committed
feat: add testing infrastructure for login MFA flow and E2E suite
Signed-off-by: romanetar <roman_ag@hotmail.com>
1 parent 3b111f5 commit ece0a61

5 files changed

Lines changed: 164 additions & 4 deletions

File tree

.github/workflows/pull_request_frontend_tests.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -54,8 +54,8 @@ jobs:
5454
PHP_VERSION: 8.3
5555
OTEL_SDK_DISABLED: true
5656
OTEL_SERVICE_ENABLED: false
57-
TURNSTILE_SITE_KEY: ''
58-
TURNSTILE_SECRET_KEY: ''
57+
TURNSTILE_SITE_KEY: ${{ secrets.TURNSTILE_SITE_KEY }}
58+
TURNSTILE_SECRET_KEY: ${{ secrets.TURNSTILE_SECRET_KEY }}
5959
services:
6060
mysql:
6161
image: mysql:8.0

.github/workflows/pull_request_unit_tests.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,8 @@ jobs:
3737
PHP_VERSION: 8.3
3838
OTEL_SDK_DISABLED: true
3939
OTEL_SERVICE_ENABLED: false
40+
TURNSTILE_SITE_KEY: ${{ secrets.TURNSTILE_SITE_KEY }}
41+
TURNSTILE_SECRET_KEY: ${{ secrets.TURNSTILE_SECRET_KEY }}
4042
services:
4143
mysql:
4244
image: mysql:8.0

.github/workflows/push.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -33,8 +33,8 @@ jobs:
3333
PHP_VERSION: 8.3
3434
OTEL_SDK_DISABLED: true
3535
OTEL_SERVICE_ENABLED: false
36-
TURNSTILE_SITE_KEY: '1x00000000000000000000AA'
37-
TURNSTILE_SECRET_KEY: '1x0000000000000000000000000000000AA'
36+
TURNSTILE_SITE_KEY: ${{ secrets.TURNSTILE_SITE_KEY }}
37+
TURNSTILE_SECRET_KEY: ${{ secrets.TURNSTILE_SECRET_KEY }}
3838
services:
3939
mysql:
4040
image: mysql:8.0
Lines changed: 129 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,129 @@
1+
name: Front End Tests On Push
2+
3+
on: push
4+
5+
jobs:
6+
7+
js-unit-tests:
8+
runs-on: ubuntu-latest
9+
steps:
10+
- name: Check out repository code
11+
uses: actions/checkout@v4
12+
- name: Set up Node.js
13+
uses: actions/setup-node@v4
14+
with:
15+
node-version: '22'
16+
cache: 'yarn'
17+
- name: Install JS dependencies
18+
run: yarn install --frozen-lockfile
19+
- name: Run Jest unit tests
20+
run: yarn test:unit:ci
21+
- name: Upload Jest coverage
22+
uses: actions/upload-artifact@v4
23+
if: always()
24+
with:
25+
name: jest-coverage
26+
path: tests/js/coverage
27+
retention-days: 5
28+
29+
e2e-tests:
30+
runs-on: ubuntu-latest
31+
env:
32+
APP_ENV: testing
33+
APP_DEBUG: true
34+
APP_KEY: base64:4vh0op/S1dAsXKQ2bbdCfWRyCI9r8NNIdPXyZWt9PX4=
35+
APP_URL: http://localhost:8001
36+
DEV_EMAIL_TO: smarcet@gmail.com
37+
DB_CONNECTION: mysql
38+
DB_HOST: 127.0.0.1
39+
DB_PORT: 3306
40+
DB_DATABASE: idp_test
41+
DB_USERNAME: root
42+
DB_PASSWORD: 1qaz2wsx
43+
REDIS_HOST: 127.0.0.1
44+
REDIS_PORT: 6379
45+
REDIS_DB: 0
46+
REDIS_PASSWORD: 1qaz2wsx
47+
REDIS_DATABASES: 16
48+
SSL_ENABLED: false
49+
SESSION_DRIVER: redis
50+
SESSION_COOKIE_SECURE: false
51+
PHP_VERSION: 8.3
52+
OTEL_SDK_DISABLED: true
53+
OTEL_SERVICE_ENABLED: false
54+
TURNSTILE_SITE_KEY: ${{ secrets.TURNSTILE_SITE_KEY }}
55+
TURNSTILE_SECRET_KEY: ${{ secrets.TURNSTILE_SECRET_KEY }}
56+
services:
57+
mysql:
58+
image: mysql:8.0
59+
env:
60+
MYSQL_ROOT_PASSWORD: 1qaz2wsx
61+
MYSQL_DATABASE: idp_test
62+
ports:
63+
- 3306:3306
64+
options: >-
65+
--health-cmd="mysqladmin ping"
66+
--health-interval=10s
67+
--health-timeout=5s
68+
--health-retries=3
69+
steps:
70+
- name: Create Redis
71+
uses: supercharge/redis-github-action@1.8.1
72+
with:
73+
redis-port: 6379
74+
redis-password: 1qaz2wsx
75+
- name: Check out repository code
76+
uses: actions/checkout@v4
77+
- name: Install PHP
78+
uses: shivammathur/setup-php@v2
79+
with:
80+
php-version: ${{ env.PHP_VERSION }}
81+
extensions: pdo_mysql, mbstring, exif, pcntl, bcmath, sockets, gettext, apcu
82+
- name: Install PHP dependencies
83+
uses: ramsey/composer-install@v3
84+
env:
85+
COMPOSER_AUTH: '{"github-oauth": {"github.com": "${{ secrets.PAT }}"} }'
86+
- name: Set up Node.js
87+
uses: actions/setup-node@v4
88+
with:
89+
node-version: '22'
90+
cache: 'yarn'
91+
- name: Install JS dependencies
92+
run: yarn install --frozen-lockfile
93+
- name: Build frontend assets
94+
run: yarn build
95+
- name: Prepare application
96+
run: |
97+
cp .env.example .env
98+
./update_doctrine.sh
99+
php artisan doctrine:migrations:migrate --no-interaction
100+
php artisan db:seed --force
101+
php artisan idp:create-super-admin test@test.com '1Qaz2wsx!'
102+
php artisan idp:create-raw-user e2e@test.com '1Qaz2wsx!'
103+
- name: Install Playwright Chromium
104+
run: npx playwright install --with-deps chromium
105+
- name: Start web server
106+
run: php artisan serve --host=127.0.0.1 --port=8001 &
107+
- name: Wait for server to be ready
108+
run: |
109+
for i in $(seq 1 20); do
110+
curl -sf http://localhost:8001 > /dev/null 2>&1 && echo "Server ready" && exit 0
111+
sleep 2
112+
done
113+
echo "Server did not start in time" && exit 1
114+
- name: Run E2E tests
115+
run: yarn test:e2e --reporter=list
116+
- name: Upload Playwright report
117+
uses: actions/upload-artifact@v4
118+
if: always()
119+
with:
120+
name: playwright-report
121+
path: tests/e2e/report
122+
retention-days: 7
123+
- name: Upload Playwright traces
124+
uses: actions/upload-artifact@v4
125+
if: failure()
126+
with:
127+
name: playwright-traces
128+
path: test-results/
129+
retention-days: 7

tests/TwoFactorLoginFlowTest.php

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
use App\libs\Auth\Models\TwoFactorAuditLog;
1717
use App\libs\Auth\Models\UserRecoveryCode;
1818
use App\libs\Auth\Models\UserTrustedDevice;
19+
use App\Mail\OAuth2PasswordlessOTPMail;
1920
use App\Services\Auth\IDeviceTrustService;
2021
use App\Services\Auth\ITwoFactorAuditService;
2122
use Auth\AuthHelper;
@@ -25,6 +26,7 @@
2526
use Illuminate\Support\Facades\Cache;
2627
use Illuminate\Support\Facades\Config;
2728
use Illuminate\Support\Facades\Hash;
29+
use Illuminate\Support\Facades\Mail;
2830
use Illuminate\Support\Facades\Session;
2931
use App\libs\OAuth2\Repositories\IOAuth2OTPRepository;
3032
use LaravelDoctrine\ORM\Facades\EntityManager;
@@ -90,6 +92,33 @@ public function testNonAdminWithoutMFALogsInNormally(): void
9092
$this->assertTrue(Auth::check(), 'a non-MFA user must get an authenticated session');
9193
}
9294

95+
// -------------------------------------------------------------------------
96+
// email delivery
97+
// -------------------------------------------------------------------------
98+
99+
public function testMFAChallengeQueuesEmailWithCorrectOTPCode(): void
100+
{
101+
$this->postLogin(self::ADMIN_EMAIL, self::SEED_PASSWORD);
102+
103+
$dbCode = $this->latestOtpCode(self::ADMIN_EMAIL);
104+
105+
Mail::assertQueued(
106+
OAuth2PasswordlessOTPMail::class,
107+
function (OAuth2PasswordlessOTPMail $mail) use ($dbCode): bool {
108+
return $mail->email === self::ADMIN_EMAIL
109+
&& $mail->otp === $dbCode;
110+
}
111+
);
112+
}
113+
114+
public function testResendMFAChallengeQueuesAdditionalEmail(): void
115+
{
116+
$this->postLogin(self::ADMIN_EMAIL, self::SEED_PASSWORD);
117+
$this->resend();
118+
119+
Mail::assertQueued(OAuth2PasswordlessOTPMail::class, 2);
120+
}
121+
93122
// -------------------------------------------------------------------------
94123
// verify2FA
95124
// -------------------------------------------------------------------------

0 commit comments

Comments
 (0)