From 8e2f0b805b03cfef042ae8f46512906aaa78cfa3 Mon Sep 17 00:00:00 2001 From: Caesar Mukama Date: Mon, 29 Jun 2026 17:18:25 +0300 Subject: [PATCH 1/2] Fix: bypass in-flight dedup when overwriteCache is set Claude-Session: https://claude.ai/code/session_01XV2QxZ65mrwUZCBjjMh1W2 --- tests/unit/lib/cachedRoute.test.js | 31 +++++++++++++++++++++++++++ workers/lib/server/lib/cachedRoute.js | 10 ++++++--- 2 files changed, 38 insertions(+), 3 deletions(-) diff --git a/tests/unit/lib/cachedRoute.test.js b/tests/unit/lib/cachedRoute.test.js index 8bff55f..a5da364 100644 --- a/tests/unit/lib/cachedRoute.test.js +++ b/tests/unit/lib/cachedRoute.test.js @@ -195,3 +195,34 @@ test('cachedRoute - null key parts', async (t) => { await cachedRoute(mockCtx, ['test', null, null], '/test', async () => ({}), false) t.pass() }) + +test('cachedRoute - overwriteCache bypasses in-flight dedup and refetches', async (t) => { + const mockCtx = { + conf: { cacheTiming: { '/test': '30s' } }, + lru_30s: { get: () => undefined, set: () => {} }, + queuedRequests: new Map() + } + + let inflightResolved = false + const inflight = cachedRoute(mockCtx, ['k'], '/test', async () => { + await new Promise(resolve => setTimeout(resolve, 50)) + inflightResolved = true + return { data: 'stale' } + }, false) + + await new Promise(resolve => setTimeout(resolve, 10)) + t.ok(mockCtx.queuedRequests.has('k'), 'a request is already in flight for the key') + + let overwriteRan = false + const fresh = await cachedRoute(mockCtx, ['k'], '/test', async () => { + overwriteRan = true + return { data: 'fresh' } + }, true) + + t.is(fresh.data, 'fresh', 'overwriteCache returns its own fresh result') + t.ok(overwriteRan, 'overwriteCache ran its own fetch instead of joining the queue') + t.absent(inflightResolved, 'overwriteCache did not block on the in-flight request') + + await inflight + t.pass() +}) diff --git a/workers/lib/server/lib/cachedRoute.js b/workers/lib/server/lib/cachedRoute.js index 234951e..32393a7 100644 --- a/workers/lib/server/lib/cachedRoute.js +++ b/workers/lib/server/lib/cachedRoute.js @@ -14,11 +14,15 @@ async function cachedRoute (ctx, ckeyParts, apiPath, func, overwriteCache = fals const ckey = ckeyParts.map(k => k ?? '-').join(':') - if (!overwriteCache) { - const cached = lru.get(ckey) - if (cached !== undefined) return cached + if (overwriteCache) { + const data = await func() + lru.set(ckey, data) + return data } + const cached = lru.get(ckey) + if (cached !== undefined) return cached + const requests = ctx.queuedRequests if (requests.has(ckey)) { From f2d8afe2d88e97d70faa1e0262a0dfe62dadce41 Mon Sep 17 00:00:00 2001 From: Caesar Mukama Date: Mon, 29 Jun 2026 17:18:25 +0300 Subject: [PATCH 2/2] Fix: map miner model name in RMA export Claude-Session: https://claude.ai/code/session_01XV2QxZ65mrwUZCBjjMh1W2 --- tests/unit/lib/work.order.export.test.js | 25 +++++++++++++++++++++ workers/lib/constants.js | 5 +++++ workers/lib/server/lib/work.order.export.js | 12 +++++++--- 3 files changed, 39 insertions(+), 3 deletions(-) diff --git a/tests/unit/lib/work.order.export.test.js b/tests/unit/lib/work.order.export.test.js index 11817f1..5584998 100644 --- a/tests/unit/lib/work.order.export.test.js +++ b/tests/unit/lib/work.order.export.test.js @@ -99,3 +99,28 @@ test('work.order.export: RMA CSV maps a MicroBT Miner WO to the fixed columns', t.is(row[9], new Date(wo.info.closedAt).toISOString().slice(0, 10), 'Repair Date from closedAt') t.is(row[10], 'eng@test', 'Engineer') }) + +test('work.order.export: RMA CSV maps a known miner type slug to its friendly model name', (t) => { + const wo = { + code: 'IVI-1-0090', + info: { + type: 1, + deviceModel: 'miner-wm-m63spp', + deviceIdentifier: 'MINER-SN-9', + createdAt: 1730000000000, + partsMoves: [{ role: 'diagnosis', partCode: 'HB-1' }] + } + } + const row = renderRmaCsv([wo]).trim().split('\r\n')[1].split(',') + t.is(row[1], 'M63S', 'Repaired type shows the friendly model name, not the worker slug') + t.is(row[8], 'M63S', 'Miner Model column shows the friendly name too') +}) + +test('work.order.export: RMA CSV leaves an unmapped deviceModel untouched', (t) => { + const wo = { + code: 'IVI-1-0091', + info: { type: 1, deviceModel: 'miner-am-s21', deviceIdentifier: 'SN', partsMoves: [] } + } + const row = renderRmaCsv([wo]).trim().split('\r\n')[1].split(',') + t.is(row[1], 'miner-am-s21', 'unmapped model falls back to the raw value') +}) diff --git a/workers/lib/constants.js b/workers/lib/constants.js index 98f5301..82c9f14 100644 --- a/workers/lib/constants.js +++ b/workers/lib/constants.js @@ -238,6 +238,10 @@ const RMA_COLUMNS = [ 'Engineer' ] +const MINER_MODEL_DISPLAY_NAMES = { + 'miner-wm-m63spp': 'M63S' +} + const HTTP_METHODS = { GET: 'GET', POST: 'POST', @@ -875,5 +879,6 @@ module.exports = { WORK_ORDER_FILE_MIME_ALLOWLIST_DEFAULT, WORK_ORDER_EXPORT_FORMATS, RMA_COLUMNS, + MINER_MODEL_DISPLAY_NAMES, MICROSOFT_AUTH_SCOPE } diff --git a/workers/lib/server/lib/work.order.export.js b/workers/lib/server/lib/work.order.export.js index 5acc1b0..0306038 100644 --- a/workers/lib/server/lib/work.order.export.js +++ b/workers/lib/server/lib/work.order.export.js @@ -1,7 +1,12 @@ 'use strict' const { csvEscape } = require('../../utils') -const { RMA_COLUMNS } = require('../../constants') +const { RMA_COLUMNS, MINER_MODEL_DISPLAY_NAMES } = require('../../constants') + +function displayMinerModel (model) { + if (!model) return model + return MINER_MODEL_DISPLAY_NAMES[String(model).toLowerCase()] || model +} function renderWorkOrderCsv (wo) { const { partsMoves, ...woFields } = wo.info || {} @@ -33,16 +38,17 @@ function renderRmaCsv (workOrders) { const repaired = moves.find(m => m.role === 'repaired') || moves.find(m => m.role === 'diagnosis') || moves[0] || {} const replaced = moves.find(m => m.role === 'replacement') || repaired const repairTs = info.closedAt ?? info.createdAt + const minerModel = displayMinerModel(info.deviceModel) return [ wo.code, - info.deviceModel, + minerModel, info.deviceIdentifier, repaired.partCode, replaced.partCode, info.issue, info.finalResult, info.remarks, - info.deviceModel, + minerModel, repairTs ? new Date(repairTs).toISOString().slice(0, 10) : '', info.assignedTo ?? info.createdBy ]