diff --git a/backend/controllers/matchController.js b/backend/controllers/matchController.js index 34899a65..cf32fcf1 100755 --- a/backend/controllers/matchController.js +++ b/backend/controllers/matchController.js @@ -247,3 +247,9 @@ export const getTtrPreviewPlayers = async (req, res) => { return res.status(error.statusCode || 500).json({ error: error.message || 'TTR-Werte konnten nicht geladen werden' }); } }; + +export const getStandaloneTtrPreviewPlayers = async (req, res) => { + // The literal /ttr-preview/standalone route has no :matchId parameter. + req.params.matchId = 'standalone'; + return getTtrPreviewPlayers(req, res); +}; diff --git a/backend/routes/matchRoutes.js b/backend/routes/matchRoutes.js index c858e45b..e073ca79 100755 --- a/backend/routes/matchRoutes.js +++ b/backend/routes/matchRoutes.js @@ -1,5 +1,5 @@ import express from 'express'; -import { uploadCSV, createManualMatch, getLeaguesForCurrentSeason, getMatchesForLeagues, getMatchesForLeague, getLeagueTable, fetchLeagueTableFromMyTischtennis, updateMatchPlayers, getPlayerMatchStats, getMatchPlayers, getTtrPreviewPlayers } from '../controllers/matchController.js'; +import { uploadCSV, createManualMatch, getLeaguesForCurrentSeason, getMatchesForLeagues, getMatchesForLeague, getLeagueTable, fetchLeagueTableFromMyTischtennis, updateMatchPlayers, getPlayerMatchStats, getMatchPlayers, getTtrPreviewPlayers, getStandaloneTtrPreviewPlayers } from '../controllers/matchController.js'; import { authenticate } from '../middleware/authMiddleware.js'; import { authorize } from '../middleware/authorizationMiddleware.js'; import multer from 'multer'; @@ -17,7 +17,7 @@ router.get('/leagues/:clubId/table/:leagueId', authenticate, authorize('schedule router.post('/leagues/:clubId/table/:leagueId/fetch', authenticate, authorize('mytischtennis', 'write'), fetchLeagueTableFromMyTischtennis); router.get('/:clubId/players', authenticate, authorize('schedule', 'read'), getMatchPlayers); router.patch('/:matchId/players', authenticate, authorize('schedule', 'write'), updateMatchPlayers); -router.post('/ttr-preview/standalone', authenticate, authorize('schedule', 'read'), getTtrPreviewPlayers); +router.post('/ttr-preview/standalone', authenticate, authorize('schedule', 'read'), getStandaloneTtrPreviewPlayers); router.post('/:matchId/ttr-preview', authenticate, authorize('schedule', 'read'), getTtrPreviewPlayers); router.get('/leagues/:clubId/stats/:leagueId', authenticate, authorize('schedule', 'read'), getPlayerMatchStats); diff --git a/backend/tests/ttrPreviewController.node.test.mjs b/backend/tests/ttrPreviewController.node.test.mjs new file mode 100644 index 00000000..c4bc75f7 --- /dev/null +++ b/backend/tests/ttrPreviewController.node.test.mjs @@ -0,0 +1,39 @@ +import test from 'node:test'; +import assert from 'node:assert/strict'; +import { readFileSync } from 'node:fs'; +import vm from 'node:vm'; + +const source = readFileSync(new URL('../controllers/matchController.js', import.meta.url), 'utf8') + .replace(/^import .*;\n/gm, '') + .replace(/^export const /gm, 'const '); + +test('the literal standalone route resolves players without a match lookup or cache mutation', async () => { + const calls = []; + const context = vm.createContext({ + console, + Op: { ne: Symbol('ne') }, + Match: { findByPk() { throw new Error('Standalone must not look up a saved match'); } }, + sequelize: { query() { throw new Error('Standalone must not access the match cache'); } }, + Member: { findAll: async () => [] }, + Club: { findAll: async () => [] }, + checkAccess: async (_token, clubId) => { assert.equal(clubId, 7); }, + myTischtennisService: {}, myTischtennisClient: {}, + resolveTtrPreview: async input => { + calls.push(input); + return { players: [{ side: 'home', name: 'Anna Wolf', ttr: 680 }], diagnostics: [] }; + }, + }); + vm.runInContext(source + '\nglobalThis.standaloneHandler = getStandaloneTtrPreviewPlayers;', context); + const req = { params: {}, headers: { authcode: 'test-token' }, body: { + clubId: 7, homePlayers: [{ firstname: 'Anna', lastname: 'Wolf' }], + }, user: { id: 3 } }; + let payload; + const res = { + status(code) { assert.fail(`Unexpected HTTP ${code}`); }, + json(body) { payload = body; }, + }; + await context.standaloneHandler(req, res); + assert.equal(calls.length, 1); + assert.equal(calls[0].homePlayers[0].firstname, 'Anna'); + assert.equal(payload.players[0].ttr, 680); +});