background-tab-runtime-module.test.js 4.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162
  1. const test = require('node:test');
  2. const assert = require('node:assert/strict');
  3. const fs = require('node:fs');
  4. test('background imports tab runtime module', () => {
  5. const source = fs.readFileSync('background.js', 'utf8');
  6. assert.match(source, /background\/tab-runtime\.js/);
  7. });
  8. test('tab runtime module exposes a factory', () => {
  9. const source = fs.readFileSync('background/tab-runtime.js', 'utf8');
  10. const globalScope = {};
  11. const api = new Function('self', `${source}; return self.MultiPageBackgroundTabRuntime;`)(globalScope);
  12. assert.equal(typeof api?.createTabRuntime, 'function');
  13. });
  14. test('tab runtime waitForTabComplete waits until tab status becomes complete', async () => {
  15. const source = fs.readFileSync('background/tab-runtime.js', 'utf8');
  16. const globalScope = {};
  17. const api = new Function('self', `${source}; return self.MultiPageBackgroundTabRuntime;`)(globalScope);
  18. let getCalls = 0;
  19. const runtime = api.createTabRuntime({
  20. LOG_PREFIX: '[test]',
  21. addLog: async () => {},
  22. buildLocalhostCleanupPrefix: () => '',
  23. chrome: {
  24. tabs: {
  25. get: async () => {
  26. getCalls += 1;
  27. return {
  28. id: 9,
  29. url: 'https://example.com',
  30. status: getCalls >= 3 ? 'complete' : 'loading',
  31. };
  32. },
  33. query: async () => [],
  34. },
  35. },
  36. getSourceLabel: (source) => source || 'unknown',
  37. getState: async () => ({ tabRegistry: {}, sourceLastUrls: {} }),
  38. matchesSourceUrlFamily: () => false,
  39. normalizeLocalCpaStep9Mode: () => 'submit',
  40. parseUrlSafely: () => null,
  41. registerTab: async () => {},
  42. setState: async () => {},
  43. shouldBypassStep9ForLocalCpa: () => false,
  44. throwIfStopped: () => {},
  45. });
  46. const result = await runtime.waitForTabComplete(9, {
  47. timeoutMs: 2000,
  48. retryDelayMs: 1,
  49. });
  50. assert.equal(result?.status, 'complete');
  51. assert.equal(getCalls, 3);
  52. });
  53. test('tab runtime waitForTabComplete aborts promptly when stop is requested', async () => {
  54. const source = fs.readFileSync('background/tab-runtime.js', 'utf8');
  55. const globalScope = {};
  56. const api = new Function('self', `${source}; return self.MultiPageBackgroundTabRuntime;`)(globalScope);
  57. let throwCalls = 0;
  58. const runtime = api.createTabRuntime({
  59. LOG_PREFIX: '[test]',
  60. addLog: async () => {},
  61. chrome: {
  62. tabs: {
  63. get: async () => ({
  64. id: 9,
  65. url: 'https://example.com',
  66. status: 'loading',
  67. }),
  68. query: async () => [],
  69. },
  70. },
  71. getSourceLabel: (sourceName) => sourceName || 'unknown',
  72. getState: async () => ({ tabRegistry: {}, sourceLastUrls: {} }),
  73. matchesSourceUrlFamily: () => false,
  74. setState: async () => {},
  75. throwIfStopped: () => {
  76. throwCalls += 1;
  77. if (throwCalls >= 2) {
  78. throw new Error('Flow stopped.');
  79. }
  80. },
  81. });
  82. await assert.rejects(
  83. runtime.waitForTabComplete(9, {
  84. timeoutMs: 2000,
  85. retryDelayMs: 1,
  86. }),
  87. /Flow stopped\./
  88. );
  89. });
  90. test('tab runtime reuses an existing matching tab when registry was cleared', async () => {
  91. const source = fs.readFileSync('background/tab-runtime.js', 'utf8');
  92. const globalScope = {};
  93. const api = new Function('self', `${source}; return self.MultiPageBackgroundTabRuntime;`)(globalScope);
  94. const tabs = [
  95. {
  96. id: 21,
  97. url: 'https://mail.phplife.net/?_task=mail&_mbox=INBOX',
  98. status: 'complete',
  99. active: false,
  100. },
  101. ];
  102. let state = { tabRegistry: {}, sourceLastUrls: {} };
  103. let createCalls = 0;
  104. const runtime = api.createTabRuntime({
  105. LOG_PREFIX: '[test]',
  106. addLog: async () => {},
  107. chrome: {
  108. tabs: {
  109. get: async (tabId) => {
  110. const tab = tabs.find((item) => item.id === tabId);
  111. if (!tab) {
  112. throw new Error('tab not found');
  113. }
  114. return { ...tab };
  115. },
  116. query: async () => tabs.map((tab) => ({ ...tab })),
  117. update: async (tabId, updates) => {
  118. const tab = tabs.find((item) => item.id === tabId);
  119. Object.assign(tab, updates);
  120. return { ...tab };
  121. },
  122. create: async () => {
  123. createCalls += 1;
  124. throw new Error('should not create a new tab');
  125. },
  126. remove: async () => {},
  127. },
  128. },
  129. getSourceLabel: (sourceName) => sourceName || 'unknown',
  130. getState: async () => state,
  131. matchesSourceUrlFamily: (sourceName, candidateUrl, referenceUrl) => (
  132. sourceName === 'mail-phplife' && candidateUrl === referenceUrl
  133. ),
  134. setState: async (updates) => {
  135. state = { ...state, ...updates };
  136. },
  137. throwIfStopped: () => {},
  138. });
  139. const tabId = await runtime.reuseOrCreateTab(
  140. 'mail-phplife',
  141. 'https://mail.phplife.net/?_task=mail&_mbox=INBOX'
  142. );
  143. assert.equal(tabId, 21);
  144. assert.equal(createCalls, 0);
  145. assert.equal(state.tabRegistry['mail-phplife']?.tabId, 21);
  146. });