Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
51 changes: 51 additions & 0 deletions .github/workflows/test-deploy.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
name: Test Suite after Deploy

on:
workflow_run:
workflows: ['Deploy API to server']
types: [completed]
workflow_dispatch:

permissions:
contents: read

jobs:
test-and-report:
runs-on: ubuntu-latest
if: ${{ github.event.workflow_run.conclusion == 'success' || github.event_name == 'workflow_dispatch' }}

steps:
- name: Checkout code
uses: actions/checkout@v4
with:
ref: master

- name: Setup PHP
uses: shivammathur/setup-php@v2
with:
php-version: '8.2'
extensions: mbstring, json

- name: Run all tests (unit + integration + E2E)
run: bash tests/run.sh
env:
E2E: 1
E2E_API_BASE: https://api.peviitor.ro

- name: Upload test report as artifact
if: always()
uses: actions/upload-artifact@v4
with:
name: test-report-deploy
path: tests/report/
retention-days: 90

- name: FTP deploy test report to api.peviitor.ro
if: always()
uses: SamKirkland/FTP-Deploy-Action@v4.3.5
with:
server: ${{ secrets.FTP_HOST }}
username: ${{ secrets.FTP_USER }}
password: ${{ secrets.FTP_PASSWD }}
local-dir: tests/report/
server-dir: /api.peviitor.ro/tests/
35 changes: 35 additions & 0 deletions .github/workflows/test-pr.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
name: Tests on PR

on:
pull_request:
branches: [master]

permissions:
contents: read
checks: write
pull-requests: write

jobs:
test:
runs-on: ubuntu-latest

steps:
- name: Checkout code
uses: actions/checkout@v4

- name: Setup PHP
uses: shivammathur/setup-php@v2
with:
php-version: '8.2'
extensions: mbstring, json

- name: Run tests (unit + integration)
run: bash tests/run.sh

- name: Upload test report
if: always()
uses: actions/upload-artifact@v4
with:
name: test-report-pr
path: tests/report/
retention-days: 30
24 changes: 23 additions & 1 deletion index.php
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,25 @@
}
.lang-toggle button:not(.active):hover { color: #5a4a3a; }

.test-badge-link {
display: inline-block;
margin-top: 0.6rem;
text-decoration: none;
}
.test-badge {
display: inline-flex;
align-items: center;
gap: 0.3rem;
font-size: 0.75rem;
font-weight: 600;
padding: 0.3rem 0.75rem;
border-radius: 8px;
background: #2d2a24;
color: #f4e9d8;
transition: opacity 0.2s;
}
.test-badge:hover { opacity: 0.8; }

/* Card */
.card {
background: #fffcf9;
Expand Down Expand Up @@ -357,7 +376,10 @@
</a>
<h1 data-i18n="brand">peviitor API</h1>
<p data-i18n="subtitle">Platformă de descoperire a joburilor — documentație API publică</p>
<div class="base-url">https://api.peviitor.ro</div>
<div class="base-url">https://api.peviitor.ro</div>
<a href="/tests/report.html" target="_blank" class="test-badge-link">
<span class="test-badge">🧪 Test Report</span>
</a>
</header>

<div class="context-box">
Expand Down
85 changes: 85 additions & 0 deletions tests/e2e/TestSmoke.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
<?php
require_once __DIR__ . '/../helpers.php';
registerTestFile(__FILE__);

$API_BASE = getenv('E2E_API_BASE') ?: 'https://api.peviitor.ro';

function apiE2E(string $method, string $path, array $headers = [], ?string $body = null): array {
$h = [];
foreach ($headers as $k => $v) {
$h[] = "$k: $v";
}
global $API_BASE;
$opts = [
'http' => [
'method' => $method,
'header' => implode("\r\n", $h),
'content' => $body,
'timeout' => 10,
'ignore_errors' => true
]
];
$url = $API_BASE . $path;
$context = stream_context_create($opts);
$response = @file_get_contents($url, false, $context);

$httpCode = 0;
if (isset($http_response_header)) {
preg_match('#HTTP/[0-9.]+ (\d+)#', $http_response_header[0], $m);
$httpCode = (int)$m[1];
}

return [
'code' => $httpCode,
'body' => $response !== false ? json_decode($response, true) : null,
'raw' => $response
];
}

test("GET /v1/random/ returns 200", function() {
$res = apiE2E('GET', '/v1/random/');
assertEqual(200, $res['code'], "Expected 200, got {$res['code']}");
assertTrue(isset($res['body']['title']), "Response should contain title");
assertTrue(isset($res['body']['company']), "Response should contain company");
assertTrue(isset($res['body']['url']), "Response should contain url");
});

test("GET /v1/random/ returns valid JSON with expected fields", function() {
$res = apiE2E('GET', '/v1/random/');
assertEqual(200, $res['code']);
assertTrue(is_string($res['body']['title'] ?? null));
assertTrue(is_string($res['body']['company'] ?? null));
assertTrue(is_array($res['body']['location'] ?? null));
assertTrue(is_string($res['body']['cif'] ?? null));
});

test("DELETE /v1/empty/ without auth returns 401", function() {
$res = apiE2E('DELETE', '/v1/empty/', [
'Content-Type' => 'application/json'
], json_encode(['confirmation' => 'DELETE_ALL_DATA']));
assertEqual(401, $res['code']);
});

test("DELETE /v1/cleanjobs/ without auth returns 401", function() {
$res = apiE2E('DELETE', '/v1/cleanjobs/', [
'Content-Type' => 'application/json'
], json_encode(['confirmation' => 'CLEAN_COMPANY_JOBS', 'company' => 'TEST']));
assertEqual(401, $res['code']);
});

test("GET /v1/empty/ returns 405", function() {
$res = apiE2E('GET', '/v1/empty/');
assertEqual(405, $res['code']);
});

test("GET /v1/cleanjobs/ returns 405", function() {
$res = apiE2E('GET', '/v1/cleanjobs/');
assertEqual(405, $res['code']);
});

test("POST /v1/random/ returns 405", function() {
$res = apiE2E('POST', '/v1/random/');
assertEqual(405, $res['code']);
});

finish();
123 changes: 123 additions & 0 deletions tests/generate-report.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
<?php
$resultsFile = $argv[1] ?? __DIR__ . '/results.json';
$results = file_exists($resultsFile) ? json_decode(file_get_contents($resultsFile), true) : [];

$allTests = [];
$total = 0;
$passed = 0;
$failed = 0;

foreach ($results as $suite) {
foreach ($suite['tests'] as $t) {
$allTests[] = $t + ['file' => $suite['file'] ?? ''];
$total++;
if ($t['pass']) $passed++; else $failed++;
}
}

$duration = array_sum(array_column($allTests, 'time'));
$pct = $total > 0 ? round($passed / $total * 100, 1) : 0;

$statusColor = $failed > 0 ? '#dc3545' : '#28a745';
$statusText = $failed > 0 ? "FAILED ($failed)" : "ALL PASSED";
?>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Test Report — peviitor API</title>
<link rel="preconnect" href="https://fonts.googleapis.com">
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&family=Fira+Code:wght@400;500&display=swap" rel="stylesheet">
<style>
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
body {
font-family: 'Inter', -apple-system, sans-serif;
background: #f5f0eb;
color: #2d2a24;
line-height: 1.6;
padding: 2rem;
}
.container { max-width: 900px; margin: 0 auto; }
h1 { font-size: 1.5rem; font-weight: 700; color: #c44536; margin-bottom: 0.5rem; }
.subtitle { color: #7d6b5a; font-size: 0.9rem; margin-bottom: 2rem; }
.summary {
background: #fffcf9;
border-radius: 12px;
padding: 1.5rem;
margin-bottom: 2rem;
box-shadow: 0 2px 12px rgba(90,60,40,0.08);
display: flex;
gap: 2rem;
flex-wrap: wrap;
}
.summary-item { text-align: center; }
.summary-item .num { font-size: 2rem; font-weight: 700; }
.summary-item .label { font-size: 0.8rem; color: #7d6b5a; text-transform: uppercase; letter-spacing: 0.04em; }
.summary .status { font-weight: 600; font-size: 1.2rem; color: <?= $statusColor ?>; display: flex; align-items: center; gap: 0.5rem; }
.test-table { width: 100%; border-collapse: collapse; background: #fffcf9; border-radius: 12px; overflow: hidden; box-shadow: 0 2px 12px rgba(90,60,40,0.08); }
.test-table th { text-align: left; padding: 0.75rem 1rem; background: #f0e4d8; font-size: 0.78rem; text-transform: uppercase; letter-spacing: 0.04em; color: #5a4a3a; font-weight: 600; }
.test-table td { padding: 0.65rem 1rem; border-bottom: 1px solid #f0e4d8; font-size: 0.9rem; }
.test-table tr:last-child td { border-bottom: none; }
.pass { color: #28a745; font-weight: 600; }
.fail { color: #dc3545; font-weight: 600; }
.error-msg { color: #dc3545; font-size: 0.82rem; font-family: 'Fira Code', monospace; background: #fff0f0; padding: 0.4rem 0.6rem; border-radius: 4px; margin-top: 0.3rem; display: inline-block; }
.file-tag { font-size: 0.75rem; color: #9a8a7a; font-family: 'Fira Code', monospace; }
.time-tag { font-size: 0.75rem; color: #9a8a7a; }
.badge { display: inline-block; padding: 0.2rem 0.6rem; border-radius: 4px; font-size: 0.75rem; font-weight: 600; }
.badge-unit { background: #e8f5e9; color: #2e7d32; }
.badge-int { background: #e3f2fd; color: #1565c0; }
.badge-e2e { background: #fff3e0; color: #e65100; }
footer { margin-top: 2rem; text-align: center; color: #9a8a7a; font-size: 0.8rem; }
</style>
</head>
<body>
<div class="container">
<h1>🧪 Test Report</h1>
<p class="subtitle">peviitor API v1 — <?= date('Y-m-d H:i:s') ?></p>

<div class="summary">
<div class="summary-item"><div class="num"><?= $total ?></div><div class="label">Total</div></div>
<div class="summary-item"><div class="num" style="color:#28a745"><?= $passed ?></div><div class="label">Passed</div></div>
<div class="summary-item"><div class="num" style="color:#dc3545"><?= $failed ?></div><div class="label">Failed</div></div>
<div class="summary-item"><div class="num"><?= $pct ?>%</div><div class="label">Pass rate</div></div>
<div class="summary-item"><div class="num"><?= round($duration, 1) ?>ms</div><div class="label">Duration</div></div>
<div class="status">● <?= $statusText ?></div>
</div>

<table class="test-table">
<thead><tr><th>Status</th><th>Test</th><th>File</th><th>Time</th></tr></thead>
<tbody>
<?php foreach ($allTests as $t): ?>
<tr>
<td><span class="<?= $t['pass'] ? 'pass' : 'fail' ?>"><?= $t['pass'] ? '✓' : '✗' ?></span></td>
<td>
<?= htmlspecialchars($t['name']) ?>
<?php if (!empty($t['error'])): ?>
<div class="error-msg"><?= htmlspecialchars($t['error']) ?></div>
<?php endif; ?>
</td>
<td class="file-tag">
<?php
$f = $t['file'];
if (str_contains($f, 'TestSolr')) echo '<span class="badge badge-unit">unit</span>';
elseif (str_contains($f, 'TestAuth')) echo '<span class="badge badge-unit">unit</span>';
elseif (str_contains($f, 'TestValidation')) echo '<span class="badge badge-unit">unit</span>';
elseif (str_contains($f, 'TestQueryBuild')) echo '<span class="badge badge-unit">unit</span>';
elseif (str_contains($f, 'TestRandom')) echo '<span class="badge badge-int">int</span>';
elseif (str_contains($f, 'TestEmpty')) echo '<span class="badge badge-int">int</span>';
elseif (str_contains($f, 'TestCleanjobs')) echo '<span class="badge badge-int">int</span>';
elseif (str_contains($f, 'TestSmoke')) echo '<span class="badge badge-e2e">e2e</span>';
else echo htmlspecialchars($f);
?>
</td>
<td class="time-tag"><?= $t['time'] ?>ms</td>
</tr>
<?php endforeach; ?>
</tbody>
</table>

<footer>Generated by peviitor API test suite</footer>
</div>
</body>
</html>
Loading
Loading