current commit
250728.4 · test: make test-inspector-network-resource sequential
// Flags: --inspect=0 --experimental-network-inspection
'use strict';
const common = require('../common');

common.skipIfInspectorDisabled();

const { NodeInstance } = require('../common/inspector-helper');
const test = require('node:test');
const assert = require('node:assert');
const path = require('path');
const fs = require('fs');

const resourceUrl = 'http://localhost:3000/app.js';
const resourcePath = path.join(__dirname, '../fixtures/inspector-network-resource/app.js.map');

const resourceText = fs.readFileSync(resourcePath, 'utf8');
const embedPath = resourcePath.replace(/\\/g, '\\\\').replace(/'/g, "\\'");
const script = `
  const { NetworkResources } = require('node:inspector');
  const fs = require('fs');
  NetworkResources.put('${resourceUrl}', fs.readFileSync('${embedPath}', 'utf8'));
  console.log('Network resource loaded:', '${resourceUrl}');
  debugger;
`;

async function setupSessionAndPauseAtEvalLastLine(script) {
  const instance = new NodeInstance([
    '--inspect-wait=0',
    '--experimental-inspector-network-resource',
  ], script);
  const session = await instance.connectInspectorSession();
  await session.send({ method: 'NodeRuntime.enable' });
  await session.waitForNotification('NodeRuntime.waitingForDebugger');
  await session.send({ method: 'Runtime.enable' });
  await session.send({ method: 'Debugger.enable' });
  await session.send({ method: 'Runtime.runIfWaitingForDebugger' });
  await session.waitForNotification('Debugger.paused');
  return { instance, session };
}

test('should load and stream a static network resource using loadNetworkResource and IO.read', async () => {
  const { session } = await setupSessionAndPauseAtEvalLastLine(script);
  const { resource } = await session.send({
    method: 'Network.loadNetworkResource',
    params: { url: resourceUrl },
  });
  assert(resource.success, 'Resource should be loaded successfully');
  assert(resource.stream, 'Resource should have a stream handle');
  let result = await session.send({ method: 'IO.read', params: { handle: resource.stream } });
  let data = result.data;
  let eof = result.eof;
  let content = '';
  while (!eof) {
    content += data;
    result = await session.send({ method: 'IO.read', params: { handle: resource.stream } });
    data = result.data;
    eof = result.eof;
  }
  content += data;
  assert.strictEqual(content, resourceText);
  await session.send({ method: 'IO.close', params: { handle: resource.stream } });
  await session.send({ method: 'Debugger.resume' });
  await session.waitForDisconnect();
});

test('should return success: false for missing resource', async () => {
  const { session } = await setupSessionAndPauseAtEvalLastLine(script);
  const { resource } = await session.send({
    method: 'Network.loadNetworkResource',
    params: { url: 'http://localhost:3000/does-not-exist.js' },
  });
  assert.strictEqual(resource.success, false);
  assert(!resource.stream, 'No stream should be returned for missing resource');
  await session.send({ method: 'Debugger.resume' });
  await session.waitForDisconnect();
});

test('should error or return empty for wrong stream id', async () => {
  const { session } = await setupSessionAndPauseAtEvalLastLine(script);
  const { resource } = await session.send({
    method: 'Network.loadNetworkResource',
    params: { url: resourceUrl },
  });
  assert(resource.success);
  const bogus = '999999';
  const result = await session.send({ method: 'IO.read', params: { handle: bogus } });
  assert(result.eof, 'Should be eof for bogus stream id');
  assert.strictEqual(result.data, '');
  await session.send({ method: 'IO.close', params: { handle: resource.stream } });
  await session.send({ method: 'Debugger.resume' });
  await session.waitForDisconnect();
});

test('should support IO.read with size and offset', async () => {
  const { session } = await setupSessionAndPauseAtEvalLastLine(script);
  const { resource } = await session.send({
    method: 'Network.loadNetworkResource',
    params: { url: resourceUrl },
  });
  assert(resource.success);
  assert(resource.stream);
  let result = await session.send({ method: 'IO.read', params: { handle: resource.stream, size: 5 } });
  assert.strictEqual(result.data, resourceText.slice(0, 5));
  result = await session.send({ method: 'IO.read', params: { handle: resource.stream, offset: 5, size: 5 } });
  assert.strictEqual(result.data, resourceText.slice(5, 10));
  result = await session.send({ method: 'IO.read', params: { handle: resource.stream, offset: 10 } });
  assert.strictEqual(result.data, resourceText.slice(10));
  await session.send({ method: 'IO.close', params: { handle: resource.stream } });
  await session.send({ method: 'Debugger.resume' });
  await session.waitForDisconnect();
});

test('should load resource put from another thread', async () => {
  const workerScript = `
    console.log('this is worker thread');
    debugger;
  `;
  const script = `
  const { NetworkResources } = require('node:inspector');
  const fs = require('fs');
  NetworkResources.put('${resourceUrl}', fs.readFileSync('${embedPath}', 'utf8'));
  const { Worker } = require('worker_threads');
  const worker = new Worker(\`${workerScript}\`, {eval: true});
  `;
  const instance = new NodeInstance([
    '--experimental-inspector-network-resource',
    '--experimental-worker-inspection',
    '--inspect-brk=0',
  ], script);
  const session = await instance.connectInspectorSession();
  await setupInspector(session);
  await session.waitForNotification('Debugger.paused');
  await session.send({ method: 'Debugger.resume' });
  await session.waitForNotification('Target.targetCreated');
  await session.send({ method: 'Target.setAutoAttach', params: { autoAttach: true, waitForDebuggerOnStart: true } });
  let sessionId;
  await session.waitForNotification((notification) => {
    if (notification.method === 'Target.attachedToTarget') {
      sessionId = notification.params.sessionId;
      return true;
    }
    return false;

  });
  await setupInspector(session, sessionId);

  await session.waitForNotification('Debugger.paused');

  const { resource } = await session.send({
    method: 'Network.loadNetworkResource',
    params: { url: resourceUrl, sessionId },
  });

  assert(resource.success, 'Resource should be loaded successfully');
  assert(resource.stream, 'Resource should have a stream handle');
  let result = await session.send({ method: 'IO.read', params: { handle: resource.stream, sessionId } });
  let data = result.data;
  let eof = result.eof;
  let content = '';
  while (!eof) {
    content += data;
    result = await session.send({ method: 'IO.read', params: { handle: resource.stream, sessionId } });
    data = result.data;
    eof = result.eof;
  }
  content += data;
  assert.strictEqual(content, resourceText);
  await session.send({ method: 'IO.close', params: { handle: resource.stream, sessionId } });

  await session.send({ method: 'Debugger.resume', sessionId });

  await session.waitForDisconnect();

  async function setupInspector(session, sessionId) {
    await session.send({ method: 'NodeRuntime.enable', sessionId });
    await session.waitForNotification('NodeRuntime.waitingForDebugger');
    await session.send({ method: 'Runtime.enable', sessionId });
    await session.send({ method: 'Debugger.enable', sessionId });
    await session.send({ method: 'Runtime.runIfWaitingForDebugger', sessionId });
  }
});