cloud-function-source.test.ts 6.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110
  1. import { readFile } from 'node:fs/promises';
  2. import { test } from 'node:test';
  3. import assert from 'node:assert/strict';
  4. const sourceUrl = new URL('../cloud-functions/saas-voc-gateway.js', import.meta.url);
  5. test('managed cloud source defines the required handler and an explicit action allowlist', async () => {
  6. const source = await readFile(sourceUrl, 'utf8');
  7. assert.match(source, /async function handler\(request, response\)/);
  8. assert.match(source, /const ACTIONS = new Set/);
  9. assert.match(source, /cloud_action_not_allowed/);
  10. assert.doesNotMatch(source, /new Parse\.Query\(input\.|new Parse\.Query\(params\./);
  11. });
  12. test('managed cloud source enforces authentication, workspace membership, roles and product scope before master-key access', async () => {
  13. const source = await readFile(sourceUrl, 'utf8');
  14. assert.match(source, /if \(!activeRequest\.user\)/);
  15. assert.match(source, /VocWorkspaceMember/);
  16. assert.match(source, /viewer_write_forbidden/);
  17. assert.match(source, /productIds/);
  18. assert.match(source, /product_scope_denied/);
  19. assert.match(source, /useMasterKey: true/);
  20. });
  21. test('managed domestic snapshot preserves the frontend dataset contract and computes empty-safe totals', async () => {
  22. const source = await readFile(sourceUrl, 'utf8');
  23. assert.match(source, /dailyTotals/);
  24. assert.match(source, /mappingGroups/);
  25. assert.match(source, /quality:/);
  26. assert.match(source, /conversionRate: total\.visitors \? /);
  27. assert.match(source, /reviewCount: reviews\.length/);
  28. });
  29. test('managed reads normalize Parse identifiers into frontend business identifiers', async () => {
  30. const source = await readFile(sourceUrl, 'utf8');
  31. assert.match(source, /function presentReadItem\(action, item\)/);
  32. assert.match(source, /output\.id = output\.publicId \|\| output\.objectId/);
  33. assert.match(source, /output\.reviewId = output\.reviewId \|\| output\.reviewKey/);
  34. assert.match(source, /listing\.products\.list/);
  35. });
  36. test('managed handler serializes shared request context and clears it after execution', async () => {
  37. const source = await readFile(sourceUrl, 'utf8');
  38. assert.match(source, /let functionQueue = Promise\.resolve\(\)/);
  39. assert.match(source, /await previous/);
  40. assert.match(source, /activeRequest = null/);
  41. assert.match(source, /activeResponse = null/);
  42. });
  43. test('managed cloud source rejects arbitrary upstream forwarding and redacts secret-shaped fields', async () => {
  44. const source = await readFile(sourceUrl, 'utf8');
  45. assert.doesNotMatch(source, /fetch\s*\(\s*input\./);
  46. assert.match(source, /token\|secret\|password\|credential\|authorization\|master/i);
  47. assert.doesNotMatch(source, /PARSE_MASTER_KEY\s*=\s*['"][^'"]+['"]/);
  48. assert.match(source, /const UPSTREAM_PATHS = \{/);
  49. assert.match(source, /upstream_path_not_allowed/);
  50. assert.match(source, /new URL\(path\.replace/);
  51. assert.match(source, /const UPSTREAM_OPERATIONS = \{/);
  52. assert.match(source, /upstream_operation_not_allowed/);
  53. assert.match(source, /upstream_timeout/);
  54. assert.match(source, /function normalizeUpstreamPath\(value\)/);
  55. assert.ok(source.includes("if (!/^https:\\/\\//i.test(base)"));
  56. });
  57. test('managed score jobs persist the worker contract and create queue items before publishing the job', async () => {
  58. const source = await readFile(sourceUrl, 'utf8');
  59. assert.match(source, /async function resolveListingScoreSources/);
  60. assert.match(source, /async function createListingScoreItems/);
  61. assert.match(source, /VocListingScoreItem/);
  62. assert.match(source, /const rubricVersion = input\.rubricVersion \|\| \(jdVocEnabled \? 'jd-voc-v0\.5'/);
  63. assert.match(source, /rubricVersion, includeAiSuggestions: scoringMode === 'ai'/);
  64. assert.match(source, /jd_voc_disabled/);
  65. assert.match(source, /\.run\$/);
  66. assert.match(source, /processed: 0, succeeded: 0, partial: 0, blocked: 0, failed: 0/);
  67. assert.match(source, /status: sources\.length \? 'initializing' : 'completed'/);
  68. assert.match(source, /await createIdempotent\('VocListingScoreJob'[\s\S]*await createListingScoreItems/);
  69. assert.match(source, /activateListingScoreJob/);
  70. assert.match(source, /function presentListingJob\(/);
  71. assert.match(source, /function presentListingJobItem\(/);
  72. });
  73. test('managed task mutations preserve persisted payloads and reject duplicate active competitor refreshes', async () => {
  74. const source = await readFile(sourceUrl, 'utf8');
  75. assert.match(source, /function storageData\(params, workspaceId\)/);
  76. assert.match(source, /storageData\(params, workspaceId\)/);
  77. assert.match(source, /activeRunQuery\.containedIn\('status', \['queued', 'running'\]\)/);
  78. assert.match(source, /competitor_listing_refresh_running/);
  79. });
  80. test('managed aggregate reads expand linked competitor scope and paginate beyond one Parse page', async () => {
  81. const source = await readFile(sourceUrl, 'utf8');
  82. assert.match(source, /\['VocProduct', 'VocCompetitorListingSnapshot', 'VocCompetitorListingChange'\]/);
  83. assert.match(source, /const competitorIds = relations\.map/);
  84. assert.match(source, /const products = await readAll\('VocProduct'/);
  85. assert.match(source, /const sources = await readAllWhere\('VocListingSourceSnapshot'/);
  86. assert.match(source, /const scores = await readAllWhere\('VocListingCurrentScore'/);
  87. assert.match(source, /const changes = await readAllWhere\('VocCompetitorListingChange'/);
  88. assert.match(source, /async function readAllWhere\(/);
  89. assert.match(source, /readAllWhere\('VocListingSourceSnapshot'/);
  90. assert.match(source, /const READ_FILTER_FIELDS = \{/);
  91. assert.match(source, /function applyReadFilters\(query, className, params\)/);
  92. assert.match(source, /query\.contains\('title', params\.search/);
  93. });
  94. test('deployment documentation exposes a separate Function registry credential boundary', async () => {
  95. const deployment = await readFile(new URL('../docs/cloud-functions-deployment.md', import.meta.url), 'utf8');
  96. assert.match(deployment, /FUNCTION_REGISTRY_SERVER_URL/);
  97. assert.match(deployment, /FUNCTION_REGISTRY_APP_ID/);
  98. assert.match(deployment, /FUNCTION_REGISTRY_MASTER_KEY/);
  99. });