plum-e2e 2.4.1 → 2.4.3

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/app.js CHANGED
@@ -47,4 +47,11 @@ if (process.env.PLUM_MODE !== 'node') {
47
47
  app.use('/trigger', require('./routes/trigger.routes'));
48
48
  }
49
49
 
50
+ // Global JSON error handler — Express's default sends HTML, which breaks JSON clients
51
+ // eslint-disable-next-line no-unused-vars
52
+ app.use((err, req, res, next) => {
53
+ console.error(err);
54
+ res.status(err.status || 500).json({ error: err.message || 'Internal server error' });
55
+ });
56
+
50
57
  module.exports = app;
@@ -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
- req.user = verifyToken(auth.slice(7));
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 };
@@ -57,10 +57,10 @@ function resolveNodeUrl(url) {
57
57
  const u = new URL(url);
58
58
  if (u.hostname === 'localhost' || u.hostname === '127.0.0.1') {
59
59
  u.hostname = 'host.docker.internal';
60
- return u.toString();
61
60
  }
61
+ return u.toString().replace(/\/+$/, '');
62
62
  } catch {}
63
- return url;
63
+ return url.replace(/\/+$/, '');
64
64
  }
65
65
 
66
66
  async function fetchRunners() {
@@ -297,10 +297,12 @@ const saveReport = async ({
297
297
  browser,
298
298
  runnerName,
299
299
  runnerId,
300
- testRunId
300
+ testRunId,
301
+ forceFail = false
301
302
  }) => {
302
303
  const normTrigger = normaliseTrigger(triggerType);
303
- const { features, status } = processCucumberJson(rawCucumberJson);
304
+ const { features, status: derivedStatus } = processCucumberJson(rawCucumberJson);
305
+ const status = forceFail ? 'FAIL' : derivedStatus;
304
306
  const cronJobId = await resolveCronJobId(normTrigger);
305
307
 
306
308
  const report = await prisma.report.create({
@@ -374,7 +376,8 @@ const saveCombinedReport = async ({
374
376
  browser,
375
377
  runnerName: runners.map((r) => r.name).join(', '),
376
378
  runnerId: null,
377
- testRunId: testRunId ?? null
379
+ testRunId: testRunId ?? null,
380
+ forceFail: reports.some((r) => r === null)
378
381
  });
379
382
  };
380
383
 
@@ -25,8 +25,10 @@ const prisma = require('./prisma');
25
25
 
26
26
  const getAll = () => prisma.runner.findMany({ orderBy: { createdAt: 'asc' } });
27
27
 
28
+ const normaliseUrl = (url) => (url ?? '').replace(/\/+$/, '');
29
+
28
30
  const create = ({ name, url, token, browser = 'chromium' }) =>
29
- prisma.runner.create({ data: { name, url, token, browser } });
31
+ prisma.runner.create({ data: { name, url: normaliseUrl(url), token, browser } });
30
32
 
31
33
  async function remove(id) {
32
34
  // Scrub the deleted runner from any cron job's runnerIds string before
@@ -45,7 +47,11 @@ async function remove(id) {
45
47
  return prisma.runner.delete({ where: { id } });
46
48
  }
47
49
 
48
- const update = (id, data) => prisma.runner.update({ where: { id }, data });
50
+ const update = (id, data) =>
51
+ prisma.runner.update({
52
+ where: { id },
53
+ data: { ...data, ...(data.url && { url: normaliseUrl(data.url) }) }
54
+ });
49
55
 
50
56
  const getById = (id) => prisma.runner.findUnique({ where: { id } });
51
57
 
@@ -61,7 +67,12 @@ async function probe({ url, token }) {
61
67
  headers: { Authorization: `Bearer ${token}` },
62
68
  signal: AbortSignal.timeout(5000)
63
69
  });
64
- return { ok: res.ok, latency: Date.now() - start };
70
+ if (!res.ok) return { ok: false, error: `HTTP ${res.status}` };
71
+ const body = await res.json();
72
+ if (!body.ok || body.mode !== 'node') {
73
+ return { ok: false, error: 'URL does not point to a Plum runner node' };
74
+ }
75
+ return { ok: true, latency: Date.now() - start };
65
76
  } catch (e) {
66
77
  return { ok: false, error: e.message };
67
78
  }
package/bin/plum.js CHANGED
@@ -318,9 +318,9 @@ async function serverStart() {
318
318
  }
319
319
  }
320
320
 
321
- clack.log.info(pc.dim('Streaming logs (Ctrl+C to detach — server keeps running):'));
322
- execSync('docker compose logs -f', { cwd: plumRoot, stdio: 'inherit' });
323
- clack.outro(pc.dim('Detached from logs. Run "docker compose down" to stop.'));
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() {
@@ -714,14 +714,30 @@
714
714
  {/if}
715
715
 
716
716
  {#each cronJobs as name}
717
- <div class="run-card cron-run" transition:fly={{ x: -4, duration: 160 }}>
717
+ <a
718
+ href="/scheduled-tests"
719
+ class="run-card cron-run"
720
+ transition:fly={{ x: -4, duration: 160 }}
721
+ >
718
722
  <span class="run-card-dot pulse-pass"></span>
719
723
  <div class="run-card-info">
720
724
  <span class="run-card-label">{name}</span>
721
725
  <span class="run-card-meta">Scheduled run</span>
722
726
  </div>
723
727
  <span class="run-card-badge cron-badge">Scheduled</span>
724
- </div>
728
+ <svg
729
+ width="13"
730
+ height="13"
731
+ viewBox="0 0 24 24"
732
+ fill="none"
733
+ stroke="currentColor"
734
+ stroke-width="2"
735
+ stroke-linecap="round"
736
+ class="run-card-arrow"
737
+ >
738
+ <line x1="5" y1="12" x2="19" y2="12" /><polyline points="12 5 19 12 12 19" />
739
+ </svg>
740
+ </a>
725
741
  {/each}
726
742
 
727
743
  {#if !anyRunning}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "plum-e2e",
3
- "version": "2.4.1",
3
+ "version": "2.4.3",
4
4
  "repository": {
5
5
  "type": "git",
6
6
  "url": "https://github.com/silverlunah/plum.git"