plum-e2e 2.4.2 → 2.4.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/backend/_scaffold/features/LoginPage.feature +5 -5
- package/backend/middleware/jwtAuth.js +14 -2
- package/backend/routes/node.routes.js +3 -1
- package/backend/services/testCaseService.js +18 -1
- package/bin/plum.js +3 -3
- package/frontend/src/routes/settings/+page.svelte +27 -10
- package/package.json +1 -1
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
@
|
|
1
|
+
@TS-001
|
|
2
2
|
Feature: Demo Sauce Login
|
|
3
3
|
|
|
4
4
|
# Background runs before every scenario in this file
|
|
@@ -6,14 +6,14 @@ Feature: Demo Sauce Login
|
|
|
6
6
|
Given I am in Demo Sauce Login page
|
|
7
7
|
|
|
8
8
|
# ── Basic Scenario ─────────────────────────────────────────────────────────
|
|
9
|
-
@
|
|
9
|
+
@TC-001
|
|
10
10
|
Scenario: User can log in with valid credentials
|
|
11
11
|
When I enter "standard_user" in username field
|
|
12
12
|
And I enter "secret_sauce" in password field
|
|
13
13
|
And I click on the login button
|
|
14
14
|
Then I should be navigated to the products page
|
|
15
15
|
|
|
16
|
-
@
|
|
16
|
+
@TC-002
|
|
17
17
|
Scenario: User cannot log in with invalid credentials
|
|
18
18
|
When I enter "invalid_user" in username field
|
|
19
19
|
And I enter "invalid_password" in password field
|
|
@@ -23,7 +23,7 @@ Feature: Demo Sauce Login
|
|
|
23
23
|
# ── Scenario Outline ───────────────────────────────────────────────────────
|
|
24
24
|
# Runs once per row in the Examples table.
|
|
25
25
|
# Use <placeholder> in steps to reference column headers.
|
|
26
|
-
@
|
|
26
|
+
@TC-003
|
|
27
27
|
Scenario Outline: User login attempts with different credentials
|
|
28
28
|
When I enter "<username>" in username field
|
|
29
29
|
And I enter "<password>" in password field
|
|
@@ -39,7 +39,7 @@ Feature: Demo Sauce Login
|
|
|
39
39
|
# ── Data Table ─────────────────────────────────────────────────────────────
|
|
40
40
|
# Pass structured data directly into a step.
|
|
41
41
|
# Access rows in your step definition via dataTable.hashes().
|
|
42
|
-
@
|
|
42
|
+
@TC-004
|
|
43
43
|
Scenario: User can log in using a data table
|
|
44
44
|
When I fill in the login form:
|
|
45
45
|
| field | value |
|
|
@@ -40,12 +40,24 @@ function jwtAuth(req, res, next) {
|
|
|
40
40
|
if (!auth || !auth.startsWith('Bearer ')) {
|
|
41
41
|
return res.status(401).json({ error: 'Unauthorized' });
|
|
42
42
|
}
|
|
43
|
+
let payload;
|
|
43
44
|
try {
|
|
44
|
-
|
|
45
|
-
next();
|
|
45
|
+
payload = verifyToken(auth.slice(7));
|
|
46
46
|
} catch {
|
|
47
47
|
return res.status(401).json({ error: 'Invalid or expired token' });
|
|
48
48
|
}
|
|
49
|
+
// Confirm the user still exists — catches stale JWTs after a DB reset
|
|
50
|
+
prisma.user
|
|
51
|
+
.findUnique({ where: { id: payload.userId }, select: { id: true } })
|
|
52
|
+
.then((user) => {
|
|
53
|
+
if (!user) return res.status(401).json({ error: 'Session expired. Please log in again.' });
|
|
54
|
+
req.user = payload;
|
|
55
|
+
next();
|
|
56
|
+
})
|
|
57
|
+
.catch(() => {
|
|
58
|
+
req.user = payload;
|
|
59
|
+
next();
|
|
60
|
+
});
|
|
49
61
|
}
|
|
50
62
|
|
|
51
63
|
module.exports = { jwtAuth };
|
|
@@ -25,6 +25,8 @@ const path = require('path');
|
|
|
25
25
|
const { authGuard } = require('../middleware/auth');
|
|
26
26
|
const { TRIGGER_REMOTE } = require('../constants/triggers');
|
|
27
27
|
|
|
28
|
+
const BACKEND_DIR = path.resolve(__dirname, '..');
|
|
29
|
+
|
|
28
30
|
// In-memory job store for active remote executions.
|
|
29
31
|
// Jobs are purged after 10 minutes post-completion.
|
|
30
32
|
const jobs = {};
|
|
@@ -84,7 +86,7 @@ router.post('/execute', authGuard, (req, res) => {
|
|
|
84
86
|
};
|
|
85
87
|
if (workers > 1) env.PARALLEL = String(workers);
|
|
86
88
|
|
|
87
|
-
const proc = spawn('npm', ['run', 'test'], { env, shell: true });
|
|
89
|
+
const proc = spawn('npm', ['run', 'test'], { env, shell: true, cwd: BACKEND_DIR });
|
|
88
90
|
proc.stdout.on('data', (d) => {
|
|
89
91
|
jobs[jobId].logs += d.toString();
|
|
90
92
|
});
|
|
@@ -15,8 +15,24 @@
|
|
|
15
15
|
* along with Plum. If not, see https://www.gnu.org/licenses/.
|
|
16
16
|
*/
|
|
17
17
|
|
|
18
|
+
const fs = require('fs');
|
|
19
|
+
const path = require('path');
|
|
18
20
|
const prisma = require('./prisma');
|
|
19
21
|
|
|
22
|
+
const FEATURES_DIR = path.join(__dirname, '../tests/features');
|
|
23
|
+
|
|
24
|
+
function isTaggedInFeatures(displayId) {
|
|
25
|
+
try {
|
|
26
|
+
const tag = `@${displayId}`;
|
|
27
|
+
return fs
|
|
28
|
+
.readdirSync(FEATURES_DIR)
|
|
29
|
+
.filter((f) => f.endsWith('.feature'))
|
|
30
|
+
.some((f) => fs.readFileSync(path.join(FEATURES_DIR, f), 'utf8').includes(tag));
|
|
31
|
+
} catch {
|
|
32
|
+
return false;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
20
36
|
const caseSelect = {
|
|
21
37
|
id: true,
|
|
22
38
|
displayId: true,
|
|
@@ -75,7 +91,8 @@ async function create({ suiteId, title, description, priority, createdById }) {
|
|
|
75
91
|
title,
|
|
76
92
|
description: description ?? '',
|
|
77
93
|
priority: priority ?? 'Medium',
|
|
78
|
-
createdById
|
|
94
|
+
createdById,
|
|
95
|
+
isAutomated: isTaggedInFeatures(displayId)
|
|
79
96
|
},
|
|
80
97
|
select: caseSelect
|
|
81
98
|
});
|
package/bin/plum.js
CHANGED
|
@@ -318,9 +318,9 @@ async function serverStart() {
|
|
|
318
318
|
}
|
|
319
319
|
}
|
|
320
320
|
|
|
321
|
-
clack.log.info(pc.
|
|
322
|
-
|
|
323
|
-
clack.outro(pc.
|
|
321
|
+
clack.log.info(`UI: ${pc.cyan(`http://localhost:${cfg.frontendPort}`)}`);
|
|
322
|
+
clack.log.info(`API: ${pc.cyan(`http://localhost:${cfg.backendPort}`)}`);
|
|
323
|
+
clack.outro(pc.green('Plum is running. Use "plum server stop" to shut down.'));
|
|
324
324
|
}
|
|
325
325
|
|
|
326
326
|
async function serverReconfig() {
|
|
@@ -578,16 +578,33 @@
|
|
|
578
578
|
2
|
|
579
579
|
);
|
|
580
580
|
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
581
|
+
const ADMIN_SECTIONS = new Set([
|
|
582
|
+
'project',
|
|
583
|
+
'runners',
|
|
584
|
+
'repository',
|
|
585
|
+
'integrations',
|
|
586
|
+
'mcp',
|
|
587
|
+
'users',
|
|
588
|
+
'backup'
|
|
589
|
+
]);
|
|
590
|
+
|
|
591
|
+
$: if ($auth.user && $auth.user.role !== 'admin' && ADMIN_SECTIONS.has(section)) {
|
|
592
|
+
section = 'account';
|
|
593
|
+
}
|
|
594
|
+
|
|
595
|
+
$: navItems =
|
|
596
|
+
$auth.user?.role === 'admin'
|
|
597
|
+
? [
|
|
598
|
+
{ id: 'project', label: 'Project' },
|
|
599
|
+
{ id: 'runners', label: 'Runners' },
|
|
600
|
+
{ id: 'repository', label: 'Repository' },
|
|
601
|
+
{ id: 'integrations', label: 'Integrations' },
|
|
602
|
+
{ id: 'mcp', label: 'MCP' },
|
|
603
|
+
{ id: 'account', label: 'Account' },
|
|
604
|
+
{ id: 'users', label: 'Users' },
|
|
605
|
+
{ id: 'backup', label: 'Backup' }
|
|
606
|
+
]
|
|
607
|
+
: [{ id: 'account', label: 'Account' }];
|
|
591
608
|
</script>
|
|
592
609
|
|
|
593
610
|
<svelte:head><title>Settings — Plum</title></svelte:head>
|