← back to Costa Rica

test/plaid-lib-timeout.test.js

77 lines

'use strict';
// lib/plaid.js now routes every live call through fetchT (PRE-FLIGHT #4/#5 class) —
// a hung Plaid connection during host bank-linking can no longer leave the request
// pending forever. Proves the wiring: a header-fast/body-stalled response rejects
// with PROVIDER_TIMEOUT, and a Plaid HTTP error fails closed (throws), never
// fabricating a token. Forces LIVE via env + require-cache reset; faked fetch.

const { test } = require('node:test');
const assert = require('node:assert');

const PLAID = require.resolve('../lib/plaid');
const realFetch = global.fetch;

function loadLivePlaid() {
  process.env.PLAID_CLIENT_ID = 'cid';
  process.env.PLAID_SECRET = 'sec';
  delete require.cache[PLAID];
  return require(PLAID);
}
function unloadLivePlaid() {
  delete process.env.PLAID_CLIENT_ID;
  delete process.env.PLAID_SECRET;
  delete require.cache[PLAID];
}

test('plaid createLinkToken(): a body-stalled Plaid response rejects PROVIDER_TIMEOUT (not an infinite hang)', async () => {
  process.env.PROVIDER_HTTP_TIMEOUT_MS = '40';
  const plaid = loadLivePlaid();
  global.fetch = (url, opts) => Promise.resolve({
    ok: true, status: 200,
    json: () => new Promise((_resolve, reject) => {
      opts.signal.addEventListener('abort', () => { const e = new Error('aborted'); e.name = 'AbortError'; reject(e); });
    }),
  });
  try {
    assert.equal(plaid.liveMode, true, 'live mode for this test');
    await assert.rejects(
      () => plaid.createLinkToken(42),
      (err) => { assert.equal(err.code, 'PROVIDER_TIMEOUT'); return true; },
    );
  } finally {
    global.fetch = realFetch;
    unloadLivePlaid();
    delete process.env.PROVIDER_HTTP_TIMEOUT_MS;
  }
});

test('plaid createLinkToken(): a Plaid HTTP error fails closed (throws, no token fabricated)', async () => {
  const plaid = loadLivePlaid();
  global.fetch = () => Promise.resolve({ ok: false, status: 400, json: async () => ({ error_code: 'INVALID_API_KEYS' }) });
  try {
    await assert.rejects(
      () => plaid.createLinkToken(42),
      (err) => { assert.match(err.message, /plaid .* HTTP 400/); assert.match(err.message, /INVALID_API_KEYS/); return true; },
    );
  } finally {
    global.fetch = realFetch;
    unloadLivePlaid();
  }
});

test('plaid createLinkToken(): sandbox (no creds) returns a deterministic fake token, no fetch', async () => {
  delete require.cache[PLAID];
  const plaid = require(PLAID); // no creds -> liveMode false
  let fetched = false;
  global.fetch = () => { fetched = true; return Promise.reject(new Error('should not fetch in sandbox')); };
  try {
    const t = await plaid.createLinkToken(42);
    assert.equal(plaid.liveMode, false);
    assert.match(t.link_token, /^link-sandbox-/);
    assert.equal(fetched, false, 'sandbox never hits the network');
  } finally {
    global.fetch = realFetch;
    delete require.cache[PLAID];
  }
});