net#AddressInfo TypeScript Examples

The following examples show how to use net#AddressInfo. You can vote up the ones you like or vote down the ones you don't like, and go to the original project or source file by following the links above each example. You may check out the related API usage on the sidebar.
Example #1
Source File: portFinder.ts    From Assistive-Webdriver with MIT License 7 votes vote down vote up
/**
 * Returns a free TCP port on the local IP address specified by the host parameter.
 *
 * @param host - Specifies the local host name or IP address to use when looking for a free TCP port.
 * @returns An unused port number chosen by the operating system.
 *
 * @remarks
 *
 * This function internally creates a TCP server with port 0 so that,
 * as mentioned in the {@link https://nodejs.org/dist/latest-v15.x/docs/api/net.html#net_server_listen_port_host_backlog_callback | node.js documentation},
 * the operating system can assign an arbitrary unused port.
 * The function then closes the server and returns the port number.
 * @public
 */
export async function getFreePort({ host }: { host: string }): Promise<number> {
  const server = createServer();
  await new Promise<void>((resolve, reject) =>
    server.listen(0, host, resolve).on("error", reject)
  );
  const address = server.address() as AddressInfo;
  const port = address.port;
  await new Promise(resolve => server.close(resolve));
  return port;
}
Example #2
Source File: utils.ts    From opentelemetry-ext-js with Apache License 2.0 6 votes vote down vote up
createServer = (callback: (server: Server, port: number) => void) => {
    const server = http.createServer();
    const sio = createServerInstance(server);
    server.listen(0, () => {
        const port = (server.address() as AddressInfo).port;
        callback(sio, port);
    });
}
Example #3
Source File: index.ts    From type-graphql-dataloader with MIT License 6 votes vote down vote up
export async function listen(
  port: number,
  resolvers: NonEmptyArray<Function>
): Promise<ListenResult> {
  const app = express();

  const schema = await buildSchema({
    resolvers,
  });

  const apollo = new ApolloServer({
    schema,
    plugins: [
      ApolloServerLoaderPlugin({
        typeormGetConnection: getConnection,
      }),
    ],
  });
  await apollo.start();

  apollo.applyMiddleware({ app, cors: false });

  const server = http.createServer(app);
  await promisify(server.listen.bind(server, port))();

  return {
    port: (server.address() as AddressInfo).port,
    close: promisify(server.close).bind(server),
  };
}
Example #4
Source File: hooks.ts    From electron-browser-shell with GNU General Public License v3.0 6 votes vote down vote up
useServer = () => {
  const emptyPage = '<script>console.log("loaded")</script>'

  // NB. extensions are only allowed on http://, https:// and ftp:// (!) urls by default.
  let server: http.Server
  let url: string

  before(async () => {
    server = http.createServer((req, res) => {
      res.end(emptyPage)
    })
    await new Promise<void>((resolve) =>
      server.listen(0, '127.0.0.1', () => {
        url = `http://127.0.0.1:${(server.address() as AddressInfo).port}`
        resolve()
      })
    )
  })
  after(() => {
    server.close()
  })

  return {
    getUrl: () => url,
  }
}
Example #5
Source File: next-fixture.ts    From dendron with GNU Affero General Public License v3.0 6 votes vote down vote up
test = base.extend<{
  port: string;
}>({
  port: [
    // eslint-disable-next-line no-empty-pattern
    async ({}, use) => {
      const app = next({
        dev: false,
        dir: path.resolve(__dirname, ".."),
      });
      await app.prepare();
      const handle = app.getRequestHandler();
      // start next server on arbitrary port
      const server: Server = await new Promise((resolve) => {
        const server = createServer((req, res) => {
          if (req.url) {
            const parsedUrl = parse(req.url, true);
            handle(req, res, parsedUrl);
          }
        });
        server.listen((error: any) => {
          if (error) throw error;
          resolve(server);
        });
      });
      // get the randomly assigned port from the server
      const port = String((server.address() as AddressInfo).port);
      // provide port to tests
      await use(port);
    },
    {
      //@ts-ignore
      scope: "worker",
    },
  ],
})
Example #6
Source File: utils.ts    From fastify-sse-v2 with MIT License 6 votes vote down vote up
export function getBaseUrl(fastifyInstance: FastifyInstance): string {
  const address = fastifyInstance.server.address() as AddressInfo;
  return `http://${address.address}:${address.port}`;
}
Example #7
Source File: index.ts    From eth2stats-dashboard with MIT License 5 votes vote down vote up
server = app.listen(Number(process.env.PORT) || 3000, process.env.HOST || "127.0.0.1", () => {
    let address = server.address() as AddressInfo;
    process.stdout.write(`Listening on ${address.address + ":" + address.port}\n`);

    opn(`http://localhost:${address.port}`);
})
Example #8
Source File: server.ts    From node-boilerplate with Apache License 2.0 5 votes vote down vote up
function serverListening(): void {
    const addressInfo: AddressInfo = <AddressInfo>server.address();
    logger.info(`Listening on ${addressInfo.address}:${env.port}`);
}
Example #9
Source File: mmdb.ts    From posthog-foss with MIT License 5 votes vote down vote up
export async function createMmdbServer(serverInstance: MMDBPrepServerInstance): Promise<net.Server> {
    status.info('?', 'Starting internal MMDB server...')
    const mmdbServer = net.createServer((socket) => {
        socket.setEncoding('utf8')

        let status: MMDBRequestStatus = MMDBRequestStatus.OK

        socket.on('data', (partialData) => {
            // partialData SHOULD be an IP address string
            let responseData: any
            if (status === MMDBRequestStatus.OK) {
                if (serverInstance.mmdb) {
                    try {
                        responseData = serverInstance.mmdb.city(partialData.toString().trim())
                    } catch (e) {
                        responseData = null
                    }
                } else {
                    captureException(new Error(status))
                    status = MMDBRequestStatus.ServiceUnavailable
                }
            }
            if (status !== MMDBRequestStatus.OK) {
                responseData = status
            }
            socket.write(serialize(responseData ?? null))
        })

        socket.setTimeout(MMDB_INTERNAL_SERVER_TIMEOUT_SECONDS * 1000).on('timeout', () => {
            captureException(new Error(status))
            status = MMDBRequestStatus.TimedOut
            socket.emit('end')
        })

        socket.once('end', () => {
            if (status !== MMDBRequestStatus.OK) {
                socket.write(serialize(status))
            }
            socket.destroy()
        })
    })

    mmdbServer.on('error', (error) => {
        captureException(error)
    })

    return new Promise((resolve, reject) => {
        const rejectTimeout = setTimeout(
            () => reject(new Error('Internal MMDB server could not start listening!')),
            3000
        )
        mmdbServer.listen(serverInstance.hub.INTERNAL_MMDB_SERVER_PORT, 'localhost', () => {
            const port = (mmdbServer.address() as AddressInfo).port
            status.info('?', `Internal MMDB server listening on port ${port}`)
            clearTimeout(rejectTimeout)
            resolve(mmdbServer)
        })
    })
}
Example #10
Source File: screenReaderMock.ts    From Assistive-Webdriver with MIT License 5 votes vote down vote up
useScreenReaderMock = () => {
  const host = "127.0.0.1";
  let server: Server;
  let port: number;
  let app: WebSocket.Server;
  const urlMap = new WeakMap<WebSocket, string>();

  beforeAll(async () => {
    server = createServer();
    app = new WebSocket.Server({ server });
    app.on("connection", (ws, req) => urlMap.set(ws, req.url!));
    server.listen(0, host);
    await new Promise((resolve, reject) =>
      server.on("listening", resolve).on("error", reject)
    );
    port = (server.address() as AddressInfo).port;
  });

  afterAll(async () => {
    await new Promise(resolve => server.close(resolve));
    await new Promise(resolve => app.close(resolve));
  });

  return {
    sendScreenReaderMessage(message: string) {
      app.clients.forEach(client => client.send(message));
    },
    getScreenReaderClients() {
      const clients: string[] = [];
      app.clients.forEach(client => clients.push(urlMap.get(client)!));
      return clients;
    },
    getScreenReaderTCPRedirection(
      vmPort = DEFAULT_VM_PORT_SCREENREADER
    ): PortRedirection {
      return {
        hostAddress: host,
        hostPort: port,
        vmPort
      };
    }
  };
}
Example #11
Source File: server.ts    From amman with Apache License 2.0 5 votes vote down vote up
static async startServer(
    ammanState: AmmanState,
    accountProviders: Record<string, AmmanAccountProvider>,
    accountRenderers: AmmanAccountRendererMap,
    programs: Program[],
    accounts: Account[],
    loadedAccountInfos: Map<string, AccountInfo<Buffer>>,
    loadedKeypairs: Map<string, Keypair>,
    accountsFolder: string,
    snapshotRoot: string,
    killRunning: boolean = true
  ): Promise<{
    app: HttpServer
    io: Server
    relayServer: RelayServer
  }> {
    if (killRunning) {
      await killRunningServer(AMMAN_RELAY_PORT)
    }
    const accountProvider = AccountProvider.fromRecord(
      accountProviders,
      accountRenderers
    )
    AccountStates.createInstance(
      accountProvider.connection,
      accountProvider,
      loadedAccountInfos,
      loadedKeypairs
    )
    const accountPersister = new AccountPersister(
      accountsFolder,
      accountProvider.connection
    )
    const snapshotPersister = new AccountPersister(
      snapshotRoot,
      accountProvider.connection
    )

    const programLabels = programs
      .filter((x) => x.label != null)
      .reduce((acc: Record<string, string>, x) => {
        acc[x.programId] = x.label!
        return acc
      }, {})

    const accountLabels = accounts
      .filter((x) => x.label != null)
      .reduce((acc: Record<string, string>, x) => {
        acc[x.accountId] = x.label!
        return acc
      }, {})

    const knownLabels = { ...programLabels, ...accountLabels }

    const { app, io, relayServer } = Relay.createApp(
      ammanState,
      accountProvider,
      accountPersister,
      snapshotPersister,
      AccountStates.instance,
      knownLabels
    )
    return new Promise((resolve, reject) => {
      app.on('error', reject).listen(AMMAN_RELAY_PORT, () => {
        const addr = app.address() as AddressInfo
        const msg = `Amman Relay listening on ${addr.address}:${addr.port}`
        logDebug(msg)
        resolve({ app, io, relayServer })
      })
    })
  }
Example #12
Source File: inoreader_collection.ts    From vscode-rss with MIT License 5 votes vote down vote up
private async authorize(): Promise<Token> {
        const server = http.createServer().listen(0, '127.0.0.1');
        const addr = await new Promise<AddressInfo>(resolve => {
            server.on('listening', () => {
                resolve(server.address() as AddressInfo);
            });
        });

        const client_id = this.cfg.appid;
        const redirect_uri = encodeURIComponent(`http://127.0.0.1:${addr.port}`);
        const url = `https://${this.domain}/oauth2/auth?client_id=${client_id}&redirect_uri=${redirect_uri}&response_type=code&scope=read+write&state=1`;
        await vscode.env.openExternal(vscode.Uri.parse(url));

        const auth_code = await vscode.window.withProgress({
            location: vscode.ProgressLocation.Notification,
            title: 'Authorizing...',
            cancellable: true
        }, async (_, token) => new Promise<string>((resolve, reject) => {
            const timer = setTimeout(() => {
                reject('Authorization Timeout');
                server.close();
            }, 300000);

            token.onCancellationRequested(() => {
                reject('Cancelled');
                server.close();
                clearInterval(timer);
            });

            server.on('request', (req: IncomingMessage, res: ServerResponse) => {
                const query = url_parse(req.url!, true).query;
                if (query.code) {
                    resolve(query.code as string);
                    res.end('<h1>Authorization Succeeded</h1>');
                    server.close();
                    clearInterval(timer);
                }
            });

        }));

        const res = await got({
            url: `https://${this.domain}/oauth2/token`,
            method: 'POST',
            form: {
                code: auth_code,
                redirect_uri: redirect_uri,
                client_id: this.cfg.appid,
                client_secret: this.cfg.appkey,
                grant_type: 'authorization_code',
            },
            throwHttpErrors: false,
        });
        const response = JSON.parse(res.body);
        if (!response.refresh_token || !response.access_token || !response.expires_in) {
            throw Error('Get Token Fail: ' + response.error_description);
        }
        return {
            auth_code: auth_code,
            refresh_token: response.refresh_token,
            access_token: response.access_token,
            expire: new Date().getTime() + response.expires_in * 1000,
        };
    }
Example #13
Source File: seleniumMock.ts    From Assistive-Webdriver with MIT License 5 votes vote down vote up
useSeleniumMock = () => {
  const host = "127.0.0.1";
  let server: Server;
  let port: number;
  let app;
  let routingFn: AsyncFnMock<void, [Koa.Context, () => Promise<any>]>;

  beforeAll(async () => {
    app = new Koa();
    app.use(async (ctx, next) => {
      if (routingFn) {
        await routingFn(ctx, next);
      } else {
        await next();
      }
    });
    server = app.listen(0, host);
    await new Promise((resolve, reject) =>
      server.on("listening", resolve).on("error", reject)
    );
    port = (server.address() as AddressInfo).port;
  });

  beforeEach(() => {
    routingFn = asyncFnMock("seleniumMock");
  });

  afterEach(async () => {
    const getConnections = promisify(server.getConnections.bind(server));
    await waitFor(async () => 0 == (await getConnections()));
  });

  afterAll(async () => {
    await new Promise(resolve => server.close(resolve));
  });

  return {
    async seleniumAnswerRequest(
      method: string,
      url: string,
      responseBody: ((ctx: Koa.Context) => Promise<any>) | Record<string, any>
    ) {
      const call = await routingFn.waitForCall();
      const ctx = call.args[0];
      expect(ctx.method).toBe(method);
      expect(ctx.url).toBe(url);
      if (typeof responseBody === "function") {
        ctx.body = await responseBody(ctx);
      } else {
        ctx.body = responseBody;
      }
      call.result.value.mockResolve();
    },
    getSeleniumTCPRedirection(
      vmPort = DEFAULT_VM_PORT_WEBDRIVER
    ): PortRedirection {
      return {
        hostAddress: host,
        hostPort: port,
        vmPort
      };
    }
  };
}
Example #14
Source File: dev-server.ts    From markmap with MIT License 5 votes vote down vote up
function setUpServer(
  transformer: Transformer,
  provider: IContentProvider,
  options: IDevelopOptions
) {
  let assets = transformer.getAssets();
  if (options.toolbar) assets = addToolbar(assets);
  const html = `${fillTemplate(
    null,
    assets
  )}<script>(${startServer.toString()})(${options.toolbar ? 60 : 0})</script>`;

  const app = new Koa();
  app.use(async (ctx, next) => {
    if (ctx.path === '/') {
      ctx.body = html;
    } else if (ctx.path === '/data') {
      const update = await provider.getUpdate(ctx.query.ts);
      const result =
        update.content == null
          ? null
          : transformer.transform(update.content || '');
      ctx.body = { ts: update.ts, result, line: update.line };
    } else {
      await next();
    }
  });

  const handle = app.callback() as http.RequestListener;
  const server = http.createServer(handle);
  server.listen(() => {
    const { port } = server.address() as AddressInfo;
    console.info(`Listening at http://localhost:${port}`);
    if (options.open) open(`http://localhost:${port}`);
  });
  let closing: Promise<void>;
  return {
    provider,
    close() {
      if (!closing) {
        closing = new Promise((resolve, reject) =>
          server.close((err?: Error) => {
            if (err) reject(err);
            else resolve();
          })
        );
      }
      return closing;
    },
  };
}
Example #15
Source File: index.ts    From hoprnet with GNU General Public License v3.0 5 votes vote down vote up
/**
 * Converts a Node.js address instance to a format that is
 * understood by Multiaddr
 * @param addr a Node.js address instance
 * @returns
 */
export function nodeToMultiaddr(addr: AddressInfo, peerId: PeerId | undefined): Multiaddr {
  let address: string
  let family: 4 | 6
  switch (addr.family) {
    case 'IPv4':
      family = 4
      // Node.js tends answer `socket.address()` calls on `udp4`
      // sockets with `::` instead of `0.0.0.0`
      if (isAnyAddress(addr.address, 'IPv6')) {
        address = '0.0.0.0'
      } else {
        address = addr.address
      }
      break
    case 'IPv6':
      family = 6
      // Make sure that we use the right any address,
      // even if this is IPv4 any address
      if (isAnyAddress(addr.address, 'IPv4')) {
        address = '::'
      } else {
        address = addr.address
      }
      break
    default:
      throw Error(`Invalid family. Got ${addr.family}`)
  }

  let ma = Multiaddr.fromNodeAddress(
    {
      family,
      address,
      port: addr.port
    },
    'tcp'
  )

  if (peerId != undefined) {
    ma = ma.encapsulate(`/p2p/${peerId.toB58String()}`)
  }

  return ma
}
Example #16
Source File: bin.ts    From Assistive-Webdriver with MIT License 4 votes vote down vote up
(async function () {
  const argv = await yargs.options({
    server: {
      type: "string",
      alias: "s",
      default: "http://localhost:3000"
    },
    browser: {
      type: "string",
      alias: "b",
      default: Browser.CHROME
    },
    capabilities: {
      type: "string",
      default: "{}"
    },
    "log-level": {
      type: "string",
      alias: "l",
      default: "info"
    },
    "public-host": {
      type: "string",
      alias: "h",
      default: `${hostname()}.`
    },
    "listen-host": {
      type: "string",
      default: "0.0.0.0"
    },
    "public-port": {
      type: "number",
      alias: "p",
      default: 0
    },
    "listen-port": {
      type: "number",
      default: 0
    },
    "vm-config": {
      type: "string",
      alias: "m"
    },
    "skip-keys": {
      type: "array",
      alias: "k"
    }
  }).argv;
  configure({
    level: argv["log-level"] as any,
    format: format.combine(format.colorize(), format.simple()),
    transports: [new transports.Console()]
  });

  const capabilities = JSON.parse(argv.capabilities);
  if (argv["vm-config"]) {
    capabilities["awd:vm-config"] = argv["vm-config"];
  }
  info(`Connecting to webdriver server at ${argv.server}`, {
    capabilities,
    browser: argv.browser
  });

  const driver = await new Builder()
    .withCapabilities(capabilities)
    .forBrowser(argv.browser)
    .usingServer(argv.server)
    .build();

  process.on("SIGINT", async () => {
    warn("Closing the session...");
    await driver.quit();
    error("Tests were interrupted.");
    process.exit(2);
  });
  sigintWin32();

  const testerSession = new TesterSession(driver);
  try {
    const eventsServerAddress = await new Promise<AddressInfo>(
      (resolve, reject) =>
        testerSession.eventsServer
          .listen(
            argv["listen-port"] || argv["public-port"],
            argv["listen-host"] || argv["public-host"],
            () => {
              resolve(testerSession.eventsServer.address() as AddressInfo);
            }
          )
          .once("error", reject)
    );

    const eventsServerListenAddress = `http://${eventsServerAddress.address}:${eventsServerAddress.port}`;
    const publicPort = argv["public-port"] || eventsServerAddress.port;
    const publicHost = argv["public-host"] || eventsServerAddress.address;
    const eventsServerPublicAddress = `http://${publicHost}:${publicPort}`;
    info(
      `Test page server started at ${eventsServerListenAddress}, VM will connect to ${eventsServerPublicAddress}`
    );
    try {
      await refreshScreenReaderText(testerSession.driver);
      info("Screen reader testing is ENABLED.");
      addScreenReaderTextListener(testerSession.driver, message =>
        info(`Screen reader said: ${message}`)
      );
      testerSession.screenReader = true;
    } catch (error) {
      info("Screen reader testing is DISABLED.");
    }
    info(`Loading test page from ${eventsServerPublicAddress}`);
    await driver.get(eventsServerPublicAddress);
    const body = await driver.findElement(By.css("body"));
    await driver.wait(until.elementTextContains(body, "ready"));
    info(`Test page was successfully loaded`);

    const testInput = await driver.findElement(By.css("#testInput"));

    await driver.actions().click(testInput).perform();

    await driver.wait(untilElementHasFocus(testInput), 10000);

    if (testerSession.screenReader) {
      await clearCachedScreenReaderText(testerSession.driver);
    }

    await driver.actions().keyDown(Key.TAB).keyUp(Key.TAB).perform();

    if (testerSession.screenReader) {
      try {
        await testerSession.driver.wait(
          forScreenReaderToSay("mysupertestlabeltocheck"),
          5000
        );
      } catch (e) {
        testerSession.reportError(`Screen reader test failed: ${e}`);
      }
    }

    const testDiv = await driver.findElement(By.css("#testDiv"));
    await driver.wait(untilElementHasFocus(testDiv), 10000);

    await testerSession.eventsQueue.getAllWaitingValues();

    await testAllKeys(testerSession, argv["skip-keys"] as any);
    await testMouseButtons(testerSession);

    if (testerSession.errorsNumber === 0) {
      info("All tests were successful!");
    } else {
      throw new Error("Some tests failed!");
    }
  } finally {
    testerSession.reportMeasures();
    await driver.quit();
    testerSession.eventsServer.close();
  }
})().catch(e => {
  error(`${e}`);
  process.exit(1);
});
Example #17
Source File: index.ts    From livepeer-com with MIT License 4 votes vote down vote up
export default async function makeApp(params: CliArgs) {
  const {
    storage,
    dbPath,
    httpPrefix,
    port,
    postgresUrl,
    cloudflareNamespace,
    cloudflareAccount,
    cloudflareAuth,
    listen = true,
    clientId,
    trustedDomain,
    kubeNamespace,
    kubeBroadcasterService,
    kubeBroadcasterTemplate,
    kubeOrchestratorService,
    kubeOrchestratorTemplate,
    fallbackProxy,
    orchestrators,
    broadcasters,
    prices,
    s3Url,
    s3UrlExternal,
    s3Access,
    s3Secret,
    upstreamBroadcaster,
    insecureTestToken,
    amqpUrl,
  } = params;
  // Storage init

  let listener: Server;
  let listenPort: number;

  const close = async () => {
    process.off("SIGTERM", sigterm);
    process.off("unhandledRejection", unhandledRejection);
    listener.close();
    await store.close();
  };

  // Handle SIGTERM gracefully. It's polite, and Kubernetes likes it.
  const sigterm = handleSigterm(close);

  process.on("SIGTERM", sigterm);

  const unhandledRejection = (err) => {
    logger.error("fatal, unhandled promise rejection: ", err);
    err.stack && logger.error(err.stack);
    sigterm();
  };
  process.on("unhandledRejection", unhandledRejection);

  const appRoute = await appRouter(params).catch((e) => {
    console.error("Error on startup");
    console.error(e);
    throw e;
    // process.exit(1)
  });
  const {
    db,
    queue,
    router,
    store,
    webhookCannon: webhook,
    taskScheduler,
  } = appRoute;

  const app = express();
  const isSilentTest =
    process.env.NODE_ENV === "test" && process.argv.indexOf("--silent") > 0;
  app.use(
    morgan("dev", {
      skip: (req, res) => {
        if (isSilentTest) {
          return true;
        }
        if (req.path.startsWith("/_next")) {
          return true;
        }
        if (insecureTestToken) {
          if (req.originalUrl.includes(insecureTestToken)) {
            return true;
          }
        }
        return false;
      },
    })
  );
  app.use(router);

  if (listen) {
    await new Promise<void>((resolve, reject) => {
      listener = app.listen(port, () => {
        const address = listener.address() as AddressInfo;
        listenPort = address.port;
        logger.info(
          `API server listening on http://0.0.0.0:${listenPort}${httpPrefix}`
        );
        resolve();
      });
      listener.on("error", (err) => {
        logger.error("Error starting server", err);
        reject(err);
      });
    });
  }

  return {
    ...params,
    app,
    listener,
    port: listenPort,
    close,
    store,
    db,
    webhook,
    taskScheduler,
    queue,
  };
}
Example #18
Source File: MacUpdater.ts    From electron-differential-updater with MIT License 4 votes vote down vote up
protected doDownloadUpdate(
    downloadUpdateOptions: DownloadUpdateOptions
  ): Promise<Array<string>> {
    this.updateInfoForPendingUpdateDownloadedEvent = null;
    const provider = downloadUpdateOptions.updateInfoAndProvider.provider;
    const files = downloadUpdateOptions.updateInfoAndProvider.provider.resolveFiles(
      downloadUpdateOptions.updateInfoAndProvider.info
    );
    const zipFileInfo = findFile(files, "zip", ["pkg", "dmg"]);
    if (zipFileInfo == null) {
      throw newError(
        `ZIP file not provided: ${safeStringifyJson(files)}`,
        "ERR_UPDATER_ZIP_FILE_NOT_FOUND"
      );
    }
    const server = createServer();
    server.on("close", () => {
      this._logger.info(
        `Proxy server for native Squirrel.Mac is closed (was started to download ${zipFileInfo.url.href})`
      );
    });
    function getServerUrl(): string {
      const address = server.address() as AddressInfo;
      return `http://127.0.0.1:${address.port}`;
    }
    return this.executeDownload({
      fileExtension: "zip",
      fileInfo: zipFileInfo,
      downloadUpdateOptions,
      task: async (destinationFile, downloadOptions) => {
        try {
          if (
            await this.differentialDownloadInstaller(
              zipFileInfo,
              downloadUpdateOptions,
              destinationFile,
              provider
            )
          ) {
            await this.httpExecutor.download(
              zipFileInfo.url,
              destinationFile,
              downloadOptions
            );
          }
        } catch (e) {
          console.log(e);
        }
      },
      done: async event => {
        const downloadedFile = event.downloadedFile;
        this.updateInfoForPendingUpdateDownloadedEvent = event;
        let updateFileSize = zipFileInfo.info.size;
        if (updateFileSize == null) {
          updateFileSize = (await stat(downloadedFile)).size;
        }

        return await new Promise<Array<string>>((resolve, reject) => {
          // insecure random is ok
          const fileUrl =
            "/" + Date.now() + "-" + Math.floor(Math.random() * 9999) + ".zip";
          server.on(
            "request",
            (request: IncomingMessage, response: ServerResponse) => {
              const requestUrl = request.url!!;
              this._logger.info(`${requestUrl} requested`);
              if (requestUrl === "/") {
                const data = Buffer.from(
                  `{ "url": "${getServerUrl()}${fileUrl}" }`
                );
                response.writeHead(200, {
                  "Content-Type": "application/json",
                  "Content-Length": data.length
                });
                response.end(data);
                return;
              }

              if (!requestUrl.startsWith(fileUrl)) {
                this._logger.warn(`${requestUrl} requested, but not supported`);
                response.writeHead(404);
                response.end();
                return;
              }

              this._logger.info(
                `${fileUrl} requested by Squirrel.Mac, pipe ${downloadedFile}`
              );

              let errorOccurred = false;
              response.on("finish", () => {
                try {
                  setImmediate(() => server.close());
                } finally {
                  if (!errorOccurred) {
                    this.nativeUpdater.removeListener("error", reject);
                    resolve([]);
                  }
                }
              });

              const readStream = createReadStream(downloadedFile);
              readStream.on("error", error => {
                try {
                  response.end();
                } catch (e) {
                  this._logger.warn(`cannot end response: ${e}`);
                }
                errorOccurred = true;
                this.nativeUpdater.removeListener("error", reject);
                reject(new Error(`Cannot pipe "${downloadedFile}": ${error}`));
              });

              response.writeHead(200, {
                "Content-Type": "application/zip",
                "Content-Length": updateFileSize
              });
              readStream.pipe(response);
            }
          );
          server.listen(0, "127.0.0.1", () => {
            this.nativeUpdater.setFeedURL({
              url: getServerUrl(),
              headers: { "Cache-Control": "no-cache" }
            });

            this.nativeUpdater.once("error", reject);
            this.nativeUpdater.checkForUpdates();
          });
        });
      }
    });
  }
Example #19
Source File: browser.test.ts    From blake3 with MIT License 4 votes vote down vote up
// Much of the browser code is also used in Node's wasm. We test things more
// thoroughly there because tests are easier to write and debug, these tests
// are primarily for sanity and checking browser-specific behavior.
describe('browser', () => {
  const addInputs = `window.inputs = ${JSON.stringify(inputs)}`;

  describe('webpack', () => {
    const testDir = resolve(tmpdir(), 'blake3-browser-test');
    let server: Server;
    let browser: Browser;
    let page: Page;

    /**
     * Builds the browser lib into the testDir.
     */
    async function buildWebpack() {
      try {
        mkdirSync(testDir);
      } catch {
        // already exists, probably
      }

      writeFileSync(
        resolve(testDir, 'entry-src.js'),
        `import("blake3/browser").then(b3 => window.blake3 = b3);`,
      );

      const stats = await new Promise<webpack.Stats>((res, rej) =>
        webpack(
          {
            mode: 'production',
            devtool: 'source-map',
            entry: resolve(testDir, 'entry-src.js'),
            output: {
              path: testDir,
              filename: 'main.js',
            },
            resolve: {
              alias: {
                'blake3/browser': resolve(__dirname, '../', 'browser.js'),
              },
            },
          },
          (err, stats) => (err ? rej(err) : res(stats)),
        ),
      );

      if (stats.hasErrors()) {
        throw stats.toString('errors-only');
      }

      writeFileSync(resolve(testDir, 'index.html'), `<script src="/main.js"></script>`);
    }

    async function serve() {
      server = createServer((req, res) => handler(req, res, { public: testDir }));
      await new Promise<void>(resolve => server.listen(0, resolve));
    }

    before(async function() {
      await buildWebpack();
      await serve();

      this.timeout(20 * 1000);

      const { port } = server.address() as AddressInfo;
      browser = await chromium.launch();
      page = await browser.newPage();
      await page.goto(`http://localhost:${port}`);
      await page.waitForFunction('!!window.blake3');
      await page.evaluate(addInputs);
    });

    runTests({
      get page() {
        return page;
      },
    });

    after(async () => {
      await browser?.close();
      server?.close();
    });
  });

  describe('native browser', () => {
    let server: Server;
    let page: Page;
    let browser: Browser;

    async function serve() {
      server = createServer((req, res) => handler(req, res, { public: resolve(__dirname, '..') }));
      await new Promise<void>(resolve => server.listen(0, resolve));
    }

    before(async function() {
      await serve();

      this.timeout(20 * 1000);

      const { port } = server.address() as AddressInfo;
      browser = await chromium.launch();
      page = await browser.newPage();
      page.on('console', console.log);
      page.on('pageerror', console.log);
      page.on('pageerror', console.log);
      await page.goto(`http://localhost:${port}/browser-async.test.html`);
      await page.waitForFunction('!!window.blake3');
      await page.evaluate(addInputs);
    });

    runTests({
      get page() {
        return page;
      },
    });

    after(async () => {
      await browser?.close();
      server.close();
    });
  });
});
Example #20
Source File: PermissionIntegrationClient.test.ts    From backstage with Apache License 2.0 4 votes vote down vote up
describe('PermissionIntegrationClient', () => {
  describe('applyConditions', () => {
    let server: SetupServerApi;

    const mockConditions: PermissionCriteria<PermissionCondition> = {
      not: {
        allOf: [
          { rule: 'RULE_1', resourceType: 'test-resource', params: [] },
          { rule: 'RULE_2', resourceType: 'test-resource', params: ['abc'] },
        ],
      },
    };

    const mockApplyConditionsHandler = jest.fn(
      (_req, res, { json }: RestContext) => {
        return res(
          json({ items: [{ id: '123', result: AuthorizeResult.ALLOW }] }),
        );
      },
    );

    const mockBaseUrl = 'http://backstage:9191';
    const discovery: PluginEndpointDiscovery = {
      async getBaseUrl(pluginId) {
        return `${mockBaseUrl}/${pluginId}`;
      },
      async getExternalBaseUrl() {
        throw new Error('Not implemented.');
      },
    };

    const client: PermissionIntegrationClient = new PermissionIntegrationClient(
      {
        discovery,
      },
    );

    beforeAll(() => {
      server = setupServer();
      server.listen({ onUnhandledRequest: 'error' });
      server.use(
        rest.post(
          `${mockBaseUrl}/plugin-1/.well-known/backstage/permissions/apply-conditions`,
          mockApplyConditionsHandler,
        ),
      );
    });

    afterAll(() => server.close());

    afterEach(() => {
      jest.clearAllMocks();
    });

    it('should make a POST request to the correct endpoint', async () => {
      await client.applyConditions('plugin-1', [
        {
          id: '123',
          resourceRef: 'testResource1',
          resourceType: 'test-resource',
          conditions: mockConditions,
        },
      ]);

      expect(mockApplyConditionsHandler).toHaveBeenCalled();
    });

    it('should include a request body', async () => {
      await client.applyConditions('plugin-1', [
        {
          id: '123',
          resourceRef: 'testResource1',
          resourceType: 'test-resource',
          conditions: mockConditions,
        },
      ]);

      expect(mockApplyConditionsHandler).toHaveBeenCalledWith(
        expect.objectContaining({
          body: {
            items: [
              {
                id: '123',
                resourceRef: 'testResource1',
                resourceType: 'test-resource',
                conditions: mockConditions,
              },
            ],
          },
        }),
        expect.anything(),
        expect.anything(),
      );
    });

    it('should return the response from the fetch request', async () => {
      const response = await client.applyConditions('plugin-1', [
        {
          id: '123',
          resourceRef: 'testResource1',
          resourceType: 'test-resource',
          conditions: mockConditions,
        },
      ]);

      expect(response).toEqual(
        expect.objectContaining([{ id: '123', result: AuthorizeResult.ALLOW }]),
      );
    });

    it('should not include authorization headers if no token is supplied', async () => {
      await client.applyConditions('plugin-1', [
        {
          id: '123',
          resourceRef: 'testResource1',
          resourceType: 'test-resource',
          conditions: mockConditions,
        },
      ]);

      const request = mockApplyConditionsHandler.mock.calls[0][0];
      expect(request.headers.has('authorization')).toEqual(false);
    });

    it('should include correctly-constructed authorization header if token is supplied', async () => {
      await client.applyConditions(
        'plugin-1',
        [
          {
            id: '123',
            resourceRef: 'testResource1',
            resourceType: 'test-resource',
            conditions: mockConditions,
          },
        ],
        'Bearer fake-token',
      );

      const request = mockApplyConditionsHandler.mock.calls[0][0];
      expect(request.headers.get('authorization')).toEqual('Bearer fake-token');
    });

    it('should forward response errors', async () => {
      mockApplyConditionsHandler.mockImplementationOnce(
        (_req, res, { status }: RestContext) => {
          return res(status(401));
        },
      );

      await expect(
        client.applyConditions('plugin-1', [
          {
            id: '123',
            resourceRef: 'testResource1',
            resourceType: 'test-resource',
            conditions: mockConditions,
          },
        ]),
      ).rejects.toThrowError(/401/i);
    });

    it('should reject invalid responses', async () => {
      mockApplyConditionsHandler.mockImplementationOnce(
        (_req, res, { json }: RestContext) => {
          return res(
            json({ items: [{ id: '123', outcome: AuthorizeResult.ALLOW }] }),
          );
        },
      );

      await expect(
        client.applyConditions('plugin-1', [
          {
            id: '123',
            resourceRef: 'testResource1',
            resourceType: 'test-resource',
            conditions: mockConditions,
          },
        ]),
      ).rejects.toThrowError(/invalid input/i);
    });

    it('should batch requests to plugin backends', async () => {
      mockApplyConditionsHandler.mockImplementationOnce(
        (_req, res, { json }: RestContext) => {
          return res(
            json({
              items: [
                { id: '123', result: AuthorizeResult.ALLOW },
                { id: '456', result: AuthorizeResult.DENY },
                { id: '789', result: AuthorizeResult.ALLOW },
              ],
            }),
          );
        },
      );

      await expect(
        client.applyConditions('plugin-1', [
          {
            id: '123',
            resourceRef: 'testResource1',
            resourceType: 'test-resource',
            conditions: mockConditions,
          },
          {
            id: '456',
            resourceRef: 'testResource1',
            resourceType: 'test-resource',
            conditions: mockConditions,
          },
          {
            id: '789',
            resourceRef: 'testResource1',
            resourceType: 'test-resource',
            conditions: mockConditions,
          },
        ]),
      ).resolves.toEqual([
        { id: '123', result: AuthorizeResult.ALLOW },
        { id: '456', result: AuthorizeResult.DENY },
        { id: '789', result: AuthorizeResult.ALLOW },
      ]);

      expect(mockApplyConditionsHandler).toHaveBeenCalledTimes(1);
    });
  });

  describe('integration with @backstage/plugin-permission-node', () => {
    let server: Server;
    let client: PermissionIntegrationClient;
    let routerSpy: RequestHandler;

    beforeAll(async () => {
      const router = Router();

      router.use(
        createPermissionIntegrationRouter({
          resourceType: 'test-resource',
          getResources: async resourceRefs =>
            resourceRefs.map(resourceRef => ({
              id: resourceRef,
            })),
          rules: [
            createPermissionRule({
              name: 'RULE_1',
              description: 'Test rule 1',
              resourceType: 'test-resource',
              apply: (_resource: any, input: 'yes' | 'no') => input === 'yes',
              toQuery: () => {
                throw new Error('Not implemented');
              },
            }),
            createPermissionRule({
              name: 'RULE_2',
              description: 'Test rule 2',
              resourceType: 'test-resource',
              apply: (_resource: any, input: 'yes' | 'no') => input === 'yes',
              toQuery: () => {
                throw new Error('Not implemented');
              },
            }),
          ],
        }),
      );

      const app = express();

      routerSpy = jest.fn(router);

      app.use('/plugin-1', routerSpy);

      await new Promise<void>(resolve => {
        server = app.listen(resolve);
      });

      const discovery: PluginEndpointDiscovery = {
        async getBaseUrl(pluginId: string) {
          const listenPort = (server.address()! as AddressInfo).port;

          return `http://0.0.0.0:${listenPort}/${pluginId}`;
        },
        async getExternalBaseUrl() {
          throw new Error('Not implemented.');
        },
      };

      client = new PermissionIntegrationClient({
        discovery,
      });
    });

    afterAll(
      async () =>
        new Promise<void>((resolve, reject) =>
          server.close(err => (err ? reject(err) : resolve())),
        ),
    );

    afterEach(() => {
      jest.clearAllMocks();
    });

    it('works for simple conditions', async () => {
      await expect(
        client.applyConditions('plugin-1', [
          {
            id: '123',
            resourceRef: 'testResource1',
            resourceType: 'test-resource',
            conditions: {
              rule: 'RULE_1',
              resourceType: 'test-resource',
              params: ['no'],
            },
          },
        ]),
      ).resolves.toEqual([{ id: '123', result: AuthorizeResult.DENY }]);
    });

    it('works for complex criteria', async () => {
      await expect(
        client.applyConditions('plugin-1', [
          {
            id: '123',
            resourceRef: 'testResource1',
            resourceType: 'test-resource',
            conditions: {
              allOf: [
                {
                  allOf: [
                    {
                      rule: 'RULE_1',
                      resourceType: 'test-resource',
                      params: ['yes'],
                    },
                    {
                      not: {
                        rule: 'RULE_2',
                        resourceType: 'test-resource',
                        params: ['no'],
                      },
                    },
                  ],
                },
                {
                  not: {
                    allOf: [
                      {
                        rule: 'RULE_1',
                        resourceType: 'test-resource',
                        params: ['no'],
                      },
                      {
                        rule: 'RULE_2',
                        resourceType: 'test-resource',
                        params: ['yes'],
                      },
                    ],
                  },
                },
              ],
            },
          },
        ]),
      ).resolves.toEqual([{ id: '123', result: AuthorizeResult.ALLOW }]);
    });
  });
});
Example #21
Source File: cli.ts    From Assistive-Webdriver with MIT License 4 votes vote down vote up
(async function () {
  const argv = await yargs.options({
    browser: {
      array: true,
      type: "string",
      alias: "b",
      default: ["chromium"]
    },
    "log-level": {
      type: "string",
      alias: "l",
      default: "info"
    },
    "public-host": {
      type: "string",
      alias: "h",
      default: `${hostname()}.`
    },
    "listen-host": {
      type: "string",
      default: "0.0.0.0"
    },
    "public-port": {
      type: "number",
      alias: "p",
      default: 0
    },
    "listen-port": {
      type: "number",
      default: 0
    },
    "vm-settings": {
      type: "string",
      alias: "m",
      demandOption: true
    },
    "server-port": {
      type: "number",
      default: 7779
    },
    "skip-keys": {
      type: "array",
      alias: "k"
    },
    "screen-reader": {
      type: "boolean",
      default: true
    }
  }).argv;
  configure({
    level: argv["log-level"] as any,
    format: format.combine(format.colorize(), format.simple()),
    transports: [new transports.Console()]
  });

  const vmSettings = JSON.parse(argv["vm-settings"]);
  info(`Starting the VM...`);
  const driver = await createVM({
    log: (entry: any) => log(entry),
    vmSettings,
    playwrightPort: argv["server-port"]
  });
  const availableBrowsers: {
    [name: string]:
      | BrowserType<ChromiumBrowser>
      | BrowserType<FirefoxBrowser>
      | BrowserType<WebKitBrowser>;
  } = {
    chromium: driver.chromium,
    firefox: driver.firefox,
    webkit: driver.webkit
  };
  const screenReader = argv["screen-reader"];
  const testerSession = new TesterSession(driver, screenReader);
  let vmDestroyed = false;

  process.on("SIGINT", async () => {
    if (!vmDestroyed) {
      warn("Stopping the VM...");
      vmDestroyed = true;
      await driver.vm.destroy();
      error("Tests were interrupted.");
      process.exit(2);
    }
  });
  sigintWin32();

  try {
    const eventsServerAddress = await new Promise<AddressInfo>(
      (resolve, reject) =>
        testerSession.eventsServer
          .listen(
            argv["listen-port"] || argv["public-port"],
            argv["listen-host"] || argv["public-host"],
            () => {
              resolve(testerSession.eventsServer.address() as AddressInfo);
            }
          )
          .once("error", reject)
    );

    const eventsServerListenAddress = `http://${eventsServerAddress.address}:${eventsServerAddress.port}`;
    const publicPort = argv["public-port"] || eventsServerAddress.port;
    const publicHost = argv["public-host"] || eventsServerAddress.address;
    const eventsServerPublicAddress = `http://${publicHost}:${publicPort}`;
    info(
      `Test page server started at ${eventsServerListenAddress}, VM will connect to ${eventsServerPublicAddress}`
    );
    if (screenReader) {
      info("Screen reader testing is ENABLED.");
      driver.screenReader.on("message", message =>
        info(`Screen reader said: ${message}`)
      );
    } else {
      info("Screen reader testing is DISABLED.");
    }

    for (const browserName of argv.browser) {
      const browser = availableBrowsers[browserName];
      if (!browser) {
        testerSession.reportError(`Unknown browser: ${browserName}`);
        continue;
      }
      info(`Testing on browser ${browserName}`);
      const browserInstance = await browser.launch({ headless: false });
      try {
        const page = await browserInstance.newPage({
          viewport: null
        });
        testerSession.page = page;
        const mouse = await driver.calibrateMouse(page);
        testerSession.mouse = mouse;
        info(`Loading test page from ${eventsServerPublicAddress}`);
        const response = await page.goto(eventsServerPublicAddress);
        if (!response?.ok) {
          testerSession.reportError(
            `Could not successfully load page ${eventsServerPublicAddress}`
          );
          continue;
        }
        info(`Test page was successfully loaded`);

        const testInput = await page.$("#testInput");
        await mouse.click(0, 0, { origin: testInput });
        await page.waitForFunction(hasFocus, testInput);

        if (screenReader) {
          await driver.screenReader.clearMessages();
        }

        await driver.keyboard.press(Key.Tab);

        if (screenReader) {
          try {
            await driver.screenReader.waitForMessage("mysupertestlabeltocheck");
          } catch (e) {
            testerSession.reportError(`Screen reader test failed: ${e}`);
          }
        }

        const testDiv = await page.$("#testDiv");
        await page.waitForFunction(hasFocus, testDiv);

        await testerSession.eventsQueue.getAllWaitingValues();

        await testAllKeys(testerSession, argv["skip-keys"] as any);
        await testMouseButtons(testerSession);
      } catch (error: any) {
        testerSession.reportError(`${error.stack || error.message || error}`);
        continue;
      } finally {
        try {
          info(`Closing browser ${browserName}...`);
          await browserInstance.close();
        } catch (e) {
          console.log(`Error in browserInstance.close()`);
        }
        info(`Tests finished for browser ${browserName}`);
      }
    }

    if (testerSession.errorsNumber === 0) {
      info("All tests were successful!");
    } else {
      throw "Some tests failed!";
    }
  } finally {
    await collectCoverage(driver.url);
    testerSession.reportMeasures();
    vmDestroyed = true;
    await driver.vm.destroy();
    testerSession.eventsServer.close();
  }
})().catch(e => {
  error(`${e.stack || e.message || e}`);
  process.exit(1);
});
Example #22
Source File: opentelemetry-express.spec.ts    From opentelemetry-ext-js with Apache License 2.0 4 votes vote down vote up
describe('opentelemetry-express', () => {
    let app: express.Application;

    before(() => {
        // registerInstrumentationTesting currently support only 1 instrumentation
        // test memory exporter initialized at beforeAll hook
        httpInstrumentation.setTracerProvider(trace.getTracerProvider());

        instrumentation.enable();
        httpInstrumentation.enable();
        app = express();
        app.use(bodyParser.json());
    });

    after(() => {
        instrumentation.disable();
        httpInstrumentation.disable();
    });

    it('express attributes', (done) => {
        const router = express.Router();
        app.use('/toto', router);
        router.post('/:id', (req, res, next) => {
            res.set('res-custom-header-key', 'res-custom-header-val');
            return res.json({ hello: 'world' });
        });

        const server = http.createServer(app);
        server.listen(0, async () => {
            const port = (server.address() as AddressInfo).port;
            const requestData = { 'req-data-key': 'req-data-val' };
            try {
                await axios.post(
                    `http://localhost:${port}/toto/tata?req-query-param-key=req-query-param-val`,
                    requestData,
                    {
                        headers: {
                            'req-custom-header-key': 'req-custom-header-val',
                        },
                    }
                );
            } catch (err) {}
            try {
                const expressSpans: ReadableSpan[] = getExpressSpans();
                expect(expressSpans.length).toBe(1);
                const span: ReadableSpan = expressSpans[0];

                // Span name
                expect(span.name).toBe('POST /toto/:id');

                // HTTP Attributes
                expect(span.attributes[SemanticAttributes.HTTP_METHOD]).toBeUndefined();
                expect(span.attributes[SemanticAttributes.HTTP_TARGET]).toBeUndefined();
                expect(span.attributes[SemanticAttributes.HTTP_SCHEME]).toBeUndefined();
                expect(span.attributes[SemanticAttributes.HTTP_STATUS_CODE]).toBeUndefined();
                expect(span.attributes[SemanticAttributes.HTTP_HOST]).toBeUndefined();
                expect(span.attributes[SemanticAttributes.HTTP_FLAVOR]).toBeUndefined();
                expect(span.attributes[SemanticAttributes.NET_PEER_IP]).toBeUndefined();

                // http span route
                const [incomingHttpSpan] = getTestSpans().filter(
                    (s) => s.kind === SpanKind.SERVER && s.instrumentationLibrary.name.includes('http')
                );
                expect(incomingHttpSpan.attributes[SemanticAttributes.HTTP_ROUTE]).toMatch('/toto/:id');
                done();
            } catch (error) {
                done(error);
            } finally {
                server.close();
            }
        });
    });

    it('express with http attributes', (done) => {
        instrumentation.disable();
        instrumentation.setConfig({
            includeHttpAttributes: true,
        });
        instrumentation.enable();

        const router = express.Router();
        app.use('/toto', router);
        router.post('/:id', (req, res, next) => {
            res.set('res-custom-header-key', 'res-custom-header-val');
            return res.json({ hello: 'world' });
        });

        const server = http.createServer(app);
        server.listen(0, async () => {
            const port = (server.address() as AddressInfo).port;
            const requestData = { 'req-data-key': 'req-data-val' };
            try {
                await axios.post(
                    `http://localhost:${port}/toto/tata?req-query-param-key=req-query-param-val`,
                    requestData,
                    {
                        headers: {
                            'req-custom-header-key': 'req-custom-header-val',
                        },
                    }
                );
            } catch (err) {}

            const expressSpans: ReadableSpan[] = getExpressSpans();
            expect(expressSpans.length).toBe(1);
            const span: ReadableSpan = expressSpans[0];

            // HTTP Attributes
            expect(span.attributes[SemanticAttributes.HTTP_METHOD]).toBe('POST');
            expect(span.attributes[SemanticAttributes.HTTP_TARGET]).toBe(
                '/toto/tata?req-query-param-key=req-query-param-val'
            );
            expect(span.attributes[SemanticAttributes.HTTP_SCHEME]).toBe('http');
            expect(span.attributes[SemanticAttributes.HTTP_STATUS_CODE]).toBe(200);
            expect(span.attributes[SemanticAttributes.HTTP_HOST]).toBe(`localhost:${port}`);
            expect(span.attributes[SemanticAttributes.HTTP_FLAVOR]).toBe('1.1');
            expect(span.attributes[SemanticAttributes.NET_PEER_IP]).toBe('::ffff:127.0.0.1');

            server.close();
            done();
        });
    });

    it('use empty res.end() to terminate response', (done) => {
        app.get('/toto', (req, res, next) => {
            res.end();
        });

        const server = http.createServer(app);
        server.listen(0, async () => {
            const port = (server.address() as AddressInfo).port;
            try {
                await axios.get(`http://localhost:${port}/toto`);
            } catch (err) {}
            const expressSpans: ReadableSpan[] = getExpressSpans();
            expect(expressSpans.length).toBe(1);
            server.close();
            done();
        });
    });

    it('mount app', (done) => {
        const subApp = express();
        subApp.get('/sub-app', (req, res) => res.end());
        app.use('/top-level-app', subApp);

        const server = http.createServer(app);
        server.listen(0, async () => {
            const port = (server.address() as AddressInfo).port;
            try {
                await axios.get(`http://localhost:${port}/top-level-app/sub-app`);
            } catch (err) {}
            const expressSpans: ReadableSpan[] = getExpressSpans();
            expect(expressSpans.length).toBe(1);

            server.close();
            done();
        });
    });

    it('should record exceptions as span events', async () => {
        app.get('/throws-exception', (req, res, next) => {
            next(new Error('internal exception'));
        });

        const server = http.createServer(app);
        await new Promise<void>((resolve) => server.listen(0, () => resolve()));
        const port = (server.address() as AddressInfo).port;
        try {
            await axios.get(`http://localhost:${port}/throws-exception`);
        } catch (err) {
            // we expect 500
        }
        const expressSpans: ReadableSpan[] = getExpressSpans();
        expect(expressSpans.length).toBe(1);
        const span: ReadableSpan = expressSpans[0];

        expect(span.events?.length).toEqual(1);
        const [event] = span.events;
        expect(event).toMatchObject({
            name: 'exception',
            attributes: {
                'exception.type': 'Error',
                'exception.message': 'internal exception',
            },
        });
        server.close();
    });

    it('should record multiple exceptions', async () => {
        app.get('/throws-exception', (req, res, next) => {
            next(new Error('internal exception'));
        });

        app.use((err, req, res, next) => {
            next(new Error('error-handling middleware exception'));
        });

        const server = http.createServer(app);
        await new Promise<void>((resolve) => server.listen(0, () => resolve()));
        const port = (server.address() as AddressInfo).port;
        try {
            await axios.get(`http://localhost:${port}/throws-exception`);
        } catch (err) {
            // we expect 500
        }
        const expressSpans: ReadableSpan[] = getExpressSpans();
        expect(expressSpans.length).toBe(1);
        const span: ReadableSpan = expressSpans[0];

        expect(span.events?.length).toEqual(2);
        const [event1, event2] = span.events;
        expect(event1).toMatchObject({
            name: 'exception',
            attributes: {
                'exception.type': 'Error',
                'exception.message': 'internal exception',
            },
        });

        expect(event2).toMatchObject({
            name: 'exception',
            attributes: {
                'exception.type': 'Error',
                'exception.message': 'error-handling middleware exception',
            },
        });
        server.close();
    });

    it('requestHook', async () => {
        instrumentation.disable();
        instrumentation.setConfig({
            requestHook: (span, requestInfo: ExpressRequestHookInformation) => {
                span.setAttribute('content_type', requestInfo.req.headers['content-type']);
            },
        });
        instrumentation.enable();

        app.post('/request-hook', (_req, res) => {
            res.sendStatus(200);
        });
        const server = http.createServer(app);
        await new Promise<void>((resolve) => server.listen(0, () => resolve()));
        const port = (server.address() as AddressInfo).port;

        await axios.post(`http://localhost:${port}/request-hook`, { rick: 'morty' });

        const expressSpans: ReadableSpan[] = getExpressSpans();
        expect(expressSpans.length).toBe(1);
        const span: ReadableSpan = expressSpans[0];
        expect(span.attributes['content_type']).toBe('application/json;charset=utf-8');
        server.close();
    });
});
Example #23
Source File: opentelemetry-express-layers.spec.ts    From opentelemetry-ext-js with Apache License 2.0 4 votes vote down vote up
describe('opentelemetry-express-layers', () => {
    let app: express.Application;

    const sendRequest = async (urlPath: string): Promise<ReadableSpan> => {
        return new Promise((resolve) => {
            const server = http.createServer(app);
            server.listen(0, async () => {
                try {
                    const port = (server.address() as AddressInfo).port;
                    try {
                        await axios.get(`http://localhost:${port}${urlPath}`, {
                            params: { 'test-param-1': 'test-param-1-value' },
                        });
                    } catch (err) {
                        // console.log(err);
                    }

                    server.close();
                    resolve(getExpressSpans()[0]);
                } catch (err) {
                    console.log(err);
                }
            });
        });
    };

    beforeEach(() => {
        instrumentation.enable();
    });

    beforeEach(() => {
        app = express();
    });

    it('no routes registered', async () => {
        await sendRequest('/no-routes');
    });

    it('app use without path', async () => {
        app.use((req, res, next) => {
            res.sendStatus(200);
        });

        const s = await sendRequest('/foo');

        // '/foo' was not consumed by any router or route. thus it's not part of the route.
        expectRouteAttributes(s, '', '/foo');
    });

    it('app use', async () => {
        app.use('/foo', (req, res, next) => {
            res.sendStatus(200);
        });

        const s = await sendRequest('/foo');

        expectRouteAttributes(s, '/foo', '/foo');
    });

    it('app use middleware with name', async () => {
        const middlewareName = (req, res, next) => {
            res.sendStatus(200);
        };
        app.use('/foo', middlewareName);

        const s = await sendRequest('/foo');
        expectRouteAttributes(s, '/foo', '/foo');
    });

    it('app use multiple middlewares', async () => {
        const middlewareName1 = (req, res, next) => {
            next();
        };
        const middlewareName2 = (req, res, next) => {
            next();
        };
        const middlewareName3 = (req, res, next) => {
            res.sendStatus(200);
        };
        app.use('/foo', middlewareName1, middlewareName2, middlewareName3);

        const s = await sendRequest('/foo');
        expectRouteAttributes(s, '/foo', '/foo');
    });

    it('multiple middlewares set as array', async () => {
        const middlewareName1 = (req, res, next) => {
            next();
        };
        const middlewareName2 = (req, res, next) => {
            next();
        };
        const middlewareName3 = (req, res, next) => {
            res.sendStatus(200);
        };
        app.use('/foo', [middlewareName1, middlewareName2, middlewareName3]);

        const s = await sendRequest('/foo');
        expectRouteAttributes(s, '/foo', '/foo');
    });

    it('router without path', async () => {
        const router = express.Router();
        router.use((req, res, next) => {
            res.sendStatus(200);
        });
        app.use('/foo', router);

        const s = await await sendRequest('/foo/bar');
        expectRouteAttributes(s, '/foo', '/foo/bar');
    });

    it('router use multiple middlewares', async () => {
        const middlewareName1 = (req, res, next) => {
            next();
        };
        const middlewareName2 = (req, res, next) => {
            next();
        };
        const middlewareName3 = (req, res, next) => {
            res.sendStatus(200);
        };
        const router = express.Router();
        router.use('/bar', middlewareName1, middlewareName2, middlewareName3);
        app.use('/foo', router);

        const s = await sendRequest('/foo/bar');
        expectRouteAttributes(s, '/foo/bar', '/foo/bar');
    });

    it('middleware chain break, expect only executed', async () => {
        const middlewareName1 = (req, res, next) => {
            next();
        };
        const middlewareName2 = (req, res, next) => {
            res.sendStatus(200);
        };
        const middlewareName3 = (req, res, next) => {
            throw new Error('middleware should not run as previous middleware did not call next');
        };
        app.use('/foo', middlewareName1, middlewareName2, middlewareName3);

        const s = await sendRequest('/foo');
        expectRouteAttributes(s, '/foo', '/foo');
    });

    it('path parameter', async () => {
        const middlewareName1 = (req, res, next) => {
            next();
        };
        const middlewareName2 = (req, res, next) => {
            res.sendStatus(200);
        };
        app.use('/foo/:id', middlewareName1, middlewareName2);

        const s = await sendRequest('/foo/19');
        expectRouteAttributes(s, '/foo/:id', '/foo/:id', { expectedParams: { id: '19' } });
    });

    it('router with use', async () => {
        const middlewareName1 = (req, res, next) => {
            res.sendStatus(200);
        };
        const router = express.Router().use(middlewareName1);
        app.use('/foo', router);

        const s = await sendRequest('/foo');
        expectRouteAttributes(s, '/foo', '/foo');
    });

    it('router with path parameters', async () => {
        const router = express.Router().all('/:id2', (req, res, next) => {
            res.sendStatus(200);
        });
        app.use('/foo/:id1', router);

        const s = await sendRequest('/foo/1/2');
        expectRouteAttributes(s, '/foo/:id1/:id2', '/foo/:id1/:id2', { expectedParams: { id1: '1', id2: '2' } });
    });

    it('route created in router with path', async () => {
        const middlewareName1 = (req, res, next) => {
            res.sendStatus(200);
        };
        app.route('/foo').get(middlewareName1);

        const s = await sendRequest('/foo');
        expectRouteAttributes(s, '/foo', '/foo');
    });

    it('multiple middlewares in route', async () => {
        const middlewareName1 = (req, res, next) => {
            next();
        };
        const middlewareName2 = (req, res, next) => {
            res.sendStatus(200);
        };
        app.route('/foo').get(middlewareName1, middlewareName2);

        const s = await sendRequest('/foo');
        expectRouteAttributes(s, '/foo', '/foo');
    });

    it('midlleware in route with http method `all`', async () => {
        const middlewareName1 = (req, res, next) => {
            res.sendStatus(200);
        };
        app.route('/foo').all(middlewareName1);

        const s = await sendRequest('/foo');
        expectRouteAttributes(s, '/foo', '/foo');
    });

    it('app with call to method', async () => {
        const middlewareName1 = (req, res, next) => {
            res.sendStatus(200);
        };
        app.get('/foo', middlewareName1);

        const s = await sendRequest('/foo');
        expectRouteAttributes(s, '/foo', '/foo');
    });

    it('middleware with two parameters', async () => {
        const middlewareName1 = (req, res) => {
            res.sendStatus(200);
        };
        app.get('/foo', middlewareName1);

        const s = await sendRequest('/foo');
        expectRouteAttributes(s, '/foo', '/foo');
    });

    it('two routers', async () => {
        const middlewareName1 = (req, res, next) => {
            res.sendStatus(200);
        };
        app.use('/foo', (req, res, next) => {
            next();
        });
        app.use('/foo', middlewareName1);

        const s = await sendRequest('/foo');
        expectRouteAttributes(s, '/foo', '/foo');
    });

    it('multi nested routers create branched tree, and finishing with finalhandler', async () => {
        const router1 = express.Router();
        router1.use('/bar', function mw1(req, res, next) {
            next();
        });
        const router2 = express.Router();
        router2.use('/bar', function mw2(req, res, next) {
            next();
        });
        app.use('/foo', router1, router2);

        const s = await sendRequest('/foo/bar');
        expectRouteFromFinalHandler(s, '/foo/bar');
    });

    it('multiple paths in app', async () => {
        app.use(['/p1', '/p2'], (req, res, next) => {
            res.sendStatus(200);
        });
        const s = await sendRequest('/p2');
        expectRouteAttributes(s, '/p2', '/p2', { configuredRoute: '["/p1","/p2"]' });
    });

    it('multiple path in router', async () => {
        const router = express.Router();
        router.use(['/p1', '/p2'], (req, res, next) => {
            res.sendStatus(200);
        });
        app.use(router);
        const s = await sendRequest('/p2');
        expectRouteAttributes(s, '/p2', '/p2', {
            configuredRoute: '["/p1","/p2"]',
        });
    });

    it('path is regex', async () => {
        app.use(new RegExp('/ab*c'), (req, res, next) => {
            res.sendStatus(200);
        });
        const s = await sendRequest('/abbbbbc');
        expectRouteAttributes(s, '/\\/ab*c/', '/\\/ab*c/');
    });

    it('error indication break middleware chain, and captured by finalhandler', async () => {
        const middlewareName1 = (req, res, next) => {
            next();
        };
        const middlewareName2 = (req, res, next) => {
            next('error on middleware 2'); // handled by finalhandler
        };
        const middlewareName3 = (req, res, next) => {
            throw new Error('middleware should not run as previous middleware did not call next');
        };
        app.use('/foo', middlewareName1, middlewareName2, middlewareName3);

        const s = await sendRequest('/foo');
        expectRouteFromFinalHandler(s, '/foo');
    });

    it('basic err capture from middleware next(err) call', async () => {
        app.use('/foo', (req, res, next) => {
            next('error message from middleware'); // handled by finalhandler
        });

        const s = await sendRequest('/foo');
        expectRouteFromFinalHandler(s, '/foo');
    });

    it('next("route") should not be marked as error', async () => {
        const middlewareStopRoute = (req, res, next) => {
            next('route'); // handled by finalhandler
        };
        const middleware2 = (req, res, next) => {
            res.sendStatus(200);
        };
        app.route('/foo').all(middlewareStopRoute, middleware2); // request fallback to finalhandler

        const s = await sendRequest('/foo');
        expectRouteFromFinalHandler(s, '/foo');
    });

    it('basic err capture from middleware throw string', async () => {
        app.use('/foo', (req, res, next) => {
            throw 'error message from middleware';
        });

        const s = await sendRequest('/foo');
        expectRouteFromFinalHandler(s, '/foo');
    });

    it('basic err capture from middleware throw exception', async () => {
        app.use('/foo', (req, res, next) => {
            throw Error('error message from middleware');
        });

        const s = await sendRequest('/foo');
        expectRouteFromFinalHandler(s, '/foo');
    });

    it('error middleware should not create a layer for non error req', async () => {
        const middlewareName1 = (req, res, next) => {
            next();
        };
        const middlewareName2 = (err, req, res, next) => {
            throw new Error('middleware should not run since its an error handling middleware');
        };
        const middlewareName3 = (req, res, next) => {
            res.sendStatus(200);
        };
        app.use('/foo', middlewareName1, middlewareName2, middlewareName3);

        const s = await sendRequest('/foo');
        expectRouteAttributes(s, '/foo', '/foo');
    });

    it('error in one route should not forward to other route', async () => {
        app.route('/foo').all(function firstRoute(req, res, next) {
            next('error in first route'); // handled by finalhandler
        });
        // second route should NOT be called and collected as we have an error
        app.route('/foo').all(function secondRoute(req, res, next) {
            res.sendStatus(200);
        });

        const s = await sendRequest('/foo');
        expectRouteFromFinalHandler(s, '/foo');
    });

    it('error middleware in layers if executed', async () => {
        const middlewareThrow = (req, res, next) => {
            next('error msg from first middleware');
        };
        const errorHandlingMiddleware = (err, req, res, next) => {
            next('some other err from error middleware');
        };

        app.use('/foo', middlewareThrow, errorHandlingMiddleware); // handled by finalhandler

        const s = await sendRequest('/foo');
        expectRouteFromFinalHandler(s, '/foo');
    });

    // express default error handler is not a middleware (implemented as a callback function),
    // so it does not create a layer
    it('error in middleware without custom handler', async () => {
        const middlewareThrow = (req, res, next) => {
            next('error msg from first middleware');
        };
        app.use('/foo', middlewareThrow); // handled by finalhandler

        const s = await sendRequest('/foo');
        expectRouteFromFinalHandler(s, '/foo');
    });

    it('multiple error middleware handlers in different levels', async () => {
        const throwingMiddleware = (req, res, next) => {
            next('error msg from first middleware');
        };
        const routerInternalErrorMiddleware = (err, req, res, next) => {
            next(err);
        };
        const generalErrorMiddleware = (err, req, res, next) => {
            next(err);
        };
        const router = express.Router().use(throwingMiddleware, routerInternalErrorMiddleware);
        app.use('/foo', router, generalErrorMiddleware);

        const s = await sendRequest('/foo');
        expectRouteFromFinalHandler(s, '/foo');
    });

    describe('express router', () => {
        describe('path trimming', () => {
            it('router without path', async () => {
                const router = express.Router();
                router.use(resEndMiddleware);
                app.use(router);
                const s = await sendRequest('/foo');
                expectRouteAttributes(s, '', '/foo');
            });

            it('router with path till end', async () => {
                const router = express.Router();
                router.use('/foo', resEndMiddleware);
                app.use(router);
                const s = await sendRequest('/foo');
                expectRouteAttributes(s, '/foo', '/foo');
            });

            it('router with strict false remove trailing slash in path end', async () => {
                const router = express.Router({ strict: false });
                router.use('/foo/', resEndMiddleware);
                app.use(router);
                const s = await sendRequest('/foo');
                expectRouteAttributes(s, '/foo', '/foo');
            });

            it('router with partial path', async () => {
                const router = express.Router();
                router.use('/foo', resEndMiddleware);
                app.use(router);
                const s = await sendRequest('/foo/bar');
                expectRouteAttributes(s, '/foo', '/foo/bar');
            });

            it('router without path registered under a path', async () => {
                const router = express.Router();
                router.use(resEndMiddleware);
                app.use('/foo', router);
                const s = await sendRequest('/foo');
                expectRouteAttributes(s, '/foo', '/foo');
            });

            it('router without path registered under a path with path leftovers', async () => {
                const router = express.Router();
                router.use(resEndMiddleware);
                app.use('/foo', router);
                const s = await sendRequest('/foo/bar');
                expectRouteAttributes(s, '/foo', '/foo/bar');
            });

            it('router with path registered under a path without leftovers', async () => {
                const router = express.Router();
                router.use('/bar', resEndMiddleware);
                app.use('/foo', router);
                const s = await sendRequest('/foo/bar');
                expectRouteAttributes(s, '/foo/bar', '/foo/bar');
            });

            it('router with path registered under a path with leftovers', async () => {
                const router = express.Router();
                router.use('/bar', resEndMiddleware);
                app.use('/foo', router);
                const s = await sendRequest('/foo/bar/baz');
                expectRouteAttributes(s, '/foo/bar', '/foo/bar/baz');
            });

            it('router with slash', async () => {
                const router = express.Router();
                router.use('/foo', resEndMiddleware);
                app.use('/', router);
                const s = await sendRequest('/foo/bar/baz');
                expectRouteAttributes(s, '/foo', '/foo/bar/baz');
            });

            it('router use with slash', async () => {
                const router = express.Router();
                router.use('/', resEndMiddleware);
                app.use('/foo', router);
                const s = await sendRequest('/foo/bar/baz');
                expectRouteAttributes(s, '/foo', '/foo/bar/baz');
            });
        });

        describe('multiple middlewares', () => {
            it('two middlewares under same router', async () => {
                app.use(express.Router().use('/foo', noopMiddleware, resEndMiddleware));
                const s = await sendRequest('/foo');
                expectRouteAttributes(s, '/foo', '/foo');
            });

            it('two middlewares under different router, second is invoked', async () => {
                const router = express.Router();
                router.use('/:firstRouteParam', noopMiddleware);
                router.use('/:secondRouteParam', resEndMiddleware);
                app.use(router);
                const s = await sendRequest('/foo');
                expectRouteAttributes(s, '/:secondRouteParam', '/:secondRouteParam', {
                    expectedParams: { secondRouteParam: 'foo' },
                });
            });

            it('two middlewares under different router, first is invoked', async () => {
                const router = express.Router();
                router.use('/:firstRouteParam', resEndMiddleware);
                router.use('/:secondRouteParam', shouldNotInvokeMiddleware);
                app.use(router);
                const s = await sendRequest('/foo');
                expectRouteAttributes(s, '/:firstRouteParam', '/:firstRouteParam', {
                    expectedParams: { firstRouteParam: 'foo' },
                });
            });
        });

        describe('multiple routers', () => {
            describe('sibling routers', () => {
                it('second router terminate req', async () => {
                    app.use(express.Router().use('/:firstRouteParam', noopMiddleware));
                    app.use(express.Router().use('/:secondRouteParam', resEndMiddleware));
                    const s = await sendRequest('/foo');
                    expectRouteAttributes(s, '/:secondRouteParam', '/:secondRouteParam', {
                        expectedParams: { secondRouteParam: 'foo' },
                    });
                });

                it('first router not matching path', async () => {
                    app.use(express.Router().use('/non-executing-path', resEndMiddleware));
                    app.use(express.Router().use('/foo', resEndMiddleware));
                    const s = await sendRequest('/foo');
                    expectRouteAttributes(s, '/foo', '/foo');
                });

                it('first router not matching path', async () => {
                    app.use(express.Router().use('/non-executing-path', resEndMiddleware));
                    app.use(express.Router().use('/foo', resEndMiddleware));
                    const s = await sendRequest('/foo');
                    expectRouteAttributes(s, '/foo', '/foo');
                });
            });

            describe('descendant', () => {
                it('routers without path', async () => {
                    const childRouter = express.Router().use(resEndMiddleware);
                    const parentRouter = express.Router().use(childRouter);
                    app.use(parentRouter);
                    const s = await sendRequest('/foo');
                    expectRouteAttributes(s, '', '/foo');
                });

                it('routers with path', async () => {
                    const childRouter = express.Router().use('/baz', resEndMiddleware);
                    const parentRouter = express.Router().use('/bar', childRouter);
                    app.use('/foo', parentRouter);
                    const s = await sendRequest('/foo/bar/baz');
                    expectRouteAttributes(s, '/foo/bar/baz', '/foo/bar/baz');
                });
            });
        });
    });

    describe('express route', () => {
        describe('registration types', () => {
            it('route.all', async () => {
                app.all('/foo', resEndMiddleware);
                const s = await sendRequest('/foo');
                expectRouteAttributes(s, '/foo', '/foo');
            });

            it('route.get', async () => {
                app.get('/foo', resEndMiddleware);
                const s = await sendRequest('/foo');
                expectRouteAttributes(s, '/foo', '/foo');
            });

            it('verb other than the one being invoked', async () => {
                // req is sent with GET but we registered POST
                // thus it should not be executed and fallback toi finalhandler
                app.post('/foo', resEndMiddleware);
                const s = await sendRequest('/foo');
                expectRouteFromFinalHandler(s, '/foo');
            });

            it('registered to route directly', async () => {
                const route = app.route('/foo');
                route.get(resEndMiddleware);
                const s = await sendRequest('/foo');
                expectRouteAttributes(s, '/foo', '/foo');
            });

            it('route with two verbs both executed', async () => {
                const route = app.route('/foo');
                route.all(noopMiddleware);
                route.get(resEndMiddleware);
                const s = await sendRequest('/foo');
                expectRouteAttributes(s, '/foo', '/foo');
            });

            it('route with slash', async () => {
                // fast_slash does not work with routes in express, thus this request will reach finalhandler
                app.all('/', resEndMiddleware);
                const s = await sendRequest('/foo');
                expectRouteFromFinalHandler(s, '/foo');
            });
        });

        describe('path manipulations', () => {
            it('route path with multiple parts', async () => {
                app.all('/foo/bar', resEndMiddleware);
                const s = await sendRequest('/foo/bar');
                expectRouteAttributes(s, '/foo/bar', '/foo/bar');
            });

            it('route path only match prefix', async () => {
                // route should match path to the end,
                // '/foo' does not match '/foo/bar'
                // thus, request is handled by finalhandler which does not set HTTP_ROUTE
                app.all('/foo', resEndMiddleware);
                const s = await sendRequest('/foo/bar');
                expectRouteFromFinalHandler(s, '/foo/bar');
            });

            it('route path slash should not be catched', async () => {
                // route should match path to the end, with not exception to slash (all paths)
                // thus, request is handled by finalhandler which does not set HTTP_ROUTE
                app.all('/', resEndMiddleware);
                const s = await sendRequest('/foo');
                expectRouteFromFinalHandler(s, '/foo');
            });
        });

        describe('multiple routes', () => {
            it('two sibling routes, first is calling res.end()', async () => {
                app.get('/:firstRouteParam', resEndMiddleware);
                app.get('/:secondRouteParam', (_req: express.Request, _res: express.Response) => {
                    throw new Error('should not be invoked');
                });
                const s = await sendRequest('/foo');
                expectRouteAttributes(s, '/:firstRouteParam', '/:firstRouteParam', {
                    expectedParams: { firstRouteParam: 'foo' },
                });
            });

            it('two sibling routes, second is calling res.end()', async () => {
                app.get('/:firstRouteParam', noopMiddleware);
                app.get('/:secondRouteParam', resEndMiddleware);
                const s = await sendRequest('/foo');
                expectRouteAttributes(s, '/:secondRouteParam', '/:secondRouteParam', {
                    expectedParams: { secondRouteParam: 'foo' },
                });
            });
        });
    });

    describe('layer multiple paths', () => {
        it('path array with single value', async () => {
            app.use(['/foo'], resEndMiddleware);
            const s = await sendRequest('/foo');
            expectRouteAttributes(s, '/foo', '/foo', { configuredRoute: '["/foo"]' });
        });

        it('two string paths, match first one', async () => {
            app.use(['/foo', '/bar'], resEndMiddleware);
            const s = await sendRequest('/foo');
            expectRouteAttributes(s, '/foo', '/foo', {
                configuredRoute: '["/foo","/bar"]',
            });
        });

        it('two string paths, match second', async () => {
            app.use(['/foo', '/bar'], resEndMiddleware);
            const s = await sendRequest('/bar');
            expectRouteAttributes(s, '/bar', '/bar', {
                configuredRoute: '["/foo","/bar"]',
            });
        });

        it('two string paths, match both', async () => {
            app.use(['/:pathParam', '/foo'], resEndMiddleware);
            const s = await sendRequest('/foo');
            // /:pathParam is first in the list, so it should match
            expectRouteAttributes(s, '/:pathParam', '/:pathParam', {
                expectedParams: { pathParam: 'foo' },
                configuredRoute: '["/:pathParam","/foo"]',
            });
        });

        it('strict mode true should capture the right path', async () => {
            const router = express.Router({ strict: true });
            router.use(['/foo/', '/foo'], resEndMiddleware);
            app.use(router);
            const s = await sendRequest('/foo');
            expectRouteAttributes(s, '/foo', '/foo', {
                configuredRoute: '["/foo","/foo"]',
            });
        });

        it('two regexp paths', async () => {
            app.use([/^\/foo$/, '/foo'], resEndMiddleware);
            const s = await sendRequest('/foo');
            // should match the regexp as it is first in the list
            expectRouteAttributes(s, '/^\\/foo$/', '/^\\/foo$/', {
                configuredRoute: '["/^\\\\/foo$/","/foo"]',
            });
        });

        it('multiple paths, non match', async () => {
            // path not matched, so it will invoke finalhandler
            app.use(['/bar', '/baz'], shouldNotInvokeMiddleware);
            const s = await sendRequest('/foo');
            expectRouteFromFinalHandler(s, '/foo');
        });

        it('multiple paths in multiple hierarchies', async () => {
            const router1 = express.Router();
            router1.all('/baz', resEndMiddleware);
            const router2 = express.Router();
            router2.use(['/foo2', '/bar2'], router1);
            app.use(['/foo1', '/bar1'], router2);
            const s = await sendRequest('/foo1/bar2/baz');
            expectRouteAttributes(s, '/foo1/bar2/baz', '/foo1/bar2/baz', {
                configuredRoute: '["/foo1","/bar1"]["/foo2","/bar2"]/baz',
            });
        });
    });

    describe('mounted app', () => {
        it('app mounted under another app', async () => {
            const mountedApp = express();
            mountedApp.use('/route-in-internal-app', resEndMiddleware);
            app.use('/mounted-app', mountedApp);
            const s = await sendRequest('/mounted-app/route-in-internal-app/foo');
            expectRouteAttributes(s, '/mounted-app/route-in-internal-app', '/mounted-app/route-in-internal-app/foo');
        });

        it('mounted app not invoked', async () => {
            const mountedApp = express();
            mountedApp.use('/route-in-internal-app', resEndMiddleware);
            app.use('/mounted-app', mountedApp);
            app.use('/foo', resEndMiddleware);
            const s = await sendRequest('/foo');
            expectRouteAttributes(s, '/foo', '/foo');
        });
    });

    describe('error middleware', () => {
        it('error middleware under app', async () => {
            app.use('/foo', errorMiddleware);
            app.use(resEndErrorMiddleware);
            const s = await sendRequest('/foo');
            expectRouteAttributes(s, '', '/foo');
        });

        it('error middleware under same use call', async () => {
            app.use('/foo', errorMiddleware, resEndErrorMiddleware);
            const s = await sendRequest('/foo');
            // in this case '/foo' path has been consumed by the error middleware in use.
            expectRouteAttributes(s, '/foo', '/foo');
        });

        it('error middleware under router', async () => {
            const router = express.Router();
            router.use('/bar', errorMiddleware);
            router.use(resEndErrorMiddleware);
            app.use('/foo', router);
            const s = await sendRequest('/foo/bar/baz');
            // in this case '/foo' path has been consumed by the error middleware in use.
            expectRouteAttributes(s, '/foo', '/foo/bar/baz');
        });

        it('error in router, handled by app', async () => {
            const router = express.Router();
            router.use('/bar', errorMiddleware);
            app.use('/foo', router);
            app.use(resEndErrorMiddleware);
            const s = await sendRequest('/foo/bar');
            expectRouteAttributes(s, '', '/foo/bar');
        });

        it('error middleware that just forward the error', async () => {
            app.use('/foo', errorMiddleware, noopErrorMiddleware);
            app.use(resEndErrorMiddleware);
            const s = await sendRequest('/foo');
            expectRouteAttributes(s, '', '/foo');
        });
    });

    describe('path params', () => {
        it('single parameter in app', async () => {
            app.use('/:id', resEndMiddleware);
            const s = await sendRequest('/1234');
            expectRouteAttributes(s, '/:id', '/:id', { expectedParams: { id: '1234' } });
        });

        it('multiple parameter in app', async () => {
            app.use('/:id1/:id2', resEndMiddleware);
            const s = await sendRequest('/1234/5678');
            expectRouteAttributes(s, '/:id1/:id2', '/:id1/:id2', { expectedParams: { id1: '1234', id2: '5678' } });
        });

        it('params from router', async () => {
            const router = express.Router();
            router.use('/:id2', resEndMiddleware);
            app.use('/:id1', router);
            const s = await sendRequest('/1234/5678');
            expectRouteAttributes(s, '/:id1/:id2', '/:id1/:id2', { expectedParams: { id1: '1234', id2: '5678' } });
        });

        describe('advanced path param', () => {
            it('two params on same path part', async () => {
                app.use('/:from-:to', resEndMiddleware);
                const s = await sendRequest('/1234-5678');
                expectRouteAttributes(s, '/:from-:to', '/:from-:to', { expectedParams: { from: '1234', to: '5678' } });
            });

            it('params with regexp', async () => {
                app.use('/:idnumeric(\\d+)', resEndMiddleware);
                const s = await sendRequest('/1234');
                expectRouteAttributes(s, '/:idnumeric(\\d+)', '/:idnumeric(\\d+)', {
                    expectedParams: { idnumeric: '1234' },
                });
            });
        });

        describe('multiple path options', () => {
            it('multiple matching path alternative should use first', async () => {
                app.use(['/:idnumeric(\\d+)', '/:idnonnumeric'], resEndMiddleware);
                const s = await sendRequest('/1234');
                expectRouteAttributes(s, '/:idnumeric(\\d+)', '/:idnumeric(\\d+)', {
                    expectedParams: { idnumeric: '1234' },
                    configuredRoute: '["/:idnumeric(\\\\d+)","/:idnonnumeric"]',
                });
            });

            it('multiple path alternative second matches', async () => {
                app.use(['/:idnumeric(\\d+)', '/:idnonnumeric'], resEndMiddleware);
                const s = await sendRequest('/text');
                expectRouteAttributes(s, '/:idnonnumeric', '/:idnonnumeric', {
                    expectedParams: { idnonnumeric: 'text' },
                    configuredRoute: '["/:idnumeric(\\\\d+)","/:idnonnumeric"]',
                });
            });
        });

        describe('param hiding', () => {
            it('same param name multiple times should capture last value', async () => {
                const router = express.Router();
                router.use('/:id', resEndMiddleware);
                app.use('/:id', router);
                const s = await sendRequest('/1234/5678');
                expectRouteAttributes(s, '/:id/:id', '/:id/:id', { expectedParams: { id: '5678' } });
            });
        });
    });

    describe('async middlewares', () => {
        it('res.end called from async context should create correct route', async () => {
            app.use('/foo', async (req, res) => {
                await new Promise((resolve) => setTimeout(resolve, 1));
                res.sendStatus(200);
            });
            const s = await sendRequest('/foo/bar');
            expectRouteAttributes(s, '/foo', '/foo/bar');
        });

        it('res.end from callback', async () => {
            app.use('/foo', (req, res) => {
                setTimeout(() => res.sendStatus(200), 2);
            });
            const s = await sendRequest('/foo/bar');
            expectRouteAttributes(s, '/foo', '/foo/bar');
        });

        it('res.end from promise then', async () => {
            app.use('/foo', (req, res) => {
                new Promise((resolve) => setTimeout(resolve, 2)).then(() => res.sendStatus(200));
            });
            const s = await sendRequest('/foo/bar');
            expectRouteAttributes(s, '/foo', '/foo/bar');
        });

        it('multiple async calling each other', async () => {
            const router = express.Router();
            router.all('/bar', async (req, res, next) => {
                await new Promise((resolve) => setTimeout(resolve, 1));
                res.sendStatus(200);
            });
            app.use(
                '/foo',
                async (req, res, next) => {
                    new Promise((resolve) => setTimeout(resolve, 2));
                    next();
                },
                router
            );
            const s = await sendRequest('/foo/bar');
            expectRouteAttributes(s, '/foo/bar', '/foo/bar');
        });

        it('async middleware runs in parallel to downstream middlewares', async () => {
            app.use('/foo', (req, res, next) => {
                next();
                new Promise((resolve) => setTimeout(resolve, 2)).then(() => res.sendStatus(200));
                res.sendStatus(200);
            });
            app.use((req, res, next) => {
                /* terminate middleware chain */
            });
            const s = await sendRequest('/foo/bar');
            expectRouteAttributes(s, '/foo', '/foo/bar');
        });

        it('thenable should not break the context', async () => {
            app.use('/foo', async (req, res, next) => {
                const thenable = {
                    then: function (onFulfilled) {
                        setTimeout(onFulfilled, 2);
                    },
                };
                // @ts-ignore
                await Promise.resolve(thenable);
                res.sendStatus(200);
            });
            const s = await sendRequest('/foo/bar');
            expectRouteAttributes(s, '/foo', '/foo/bar');
        });
    });
});
Example #24
Source File: pluginsServer.ts    From posthog-foss with MIT License 4 votes vote down vote up
export async function startPluginsServer(
    config: Partial<PluginsServerConfig>,
    makePiscina: (config: PluginsServerConfig) => Piscina
): Promise<ServerInstance> {
    const serverConfig: PluginsServerConfig = {
        ...defaultConfig,
        ...config,
    }

    status.info('ℹ️', `${serverConfig.WORKER_CONCURRENCY} workers, ${serverConfig.TASKS_PER_WORKER} tasks per worker`)

    let pubSub: PubSub | undefined
    let hub: Hub | undefined
    let actionsReloadJob: schedule.Job | undefined
    let pingJob: schedule.Job | undefined
    let piscinaStatsJob: schedule.Job | undefined
    let internalMetricsStatsJob: schedule.Job | undefined
    let pluginMetricsJob: schedule.Job | undefined
    let piscina: Piscina | undefined
    let queue: Queue | undefined // ingestion queue
    let redisQueueForPluginJobs: Queue | undefined | null
    let jobQueueConsumer: JobQueueConsumerControl | undefined
    let closeHub: () => Promise<void> | undefined
    let scheduleControl: ScheduleControl | undefined
    let mmdbServer: net.Server | undefined
    let lastActivityCheck: NodeJS.Timeout | undefined

    let shutdownStatus = 0

    async function closeJobs(): Promise<void> {
        shutdownStatus += 1
        if (shutdownStatus === 2) {
            status.info('?', 'Try again to shut down forcibly')
            return
        }
        if (shutdownStatus >= 3) {
            status.info('❗️', 'Shutting down forcibly!')
            void piscina?.destroy()
            process.exit()
        }
        status.info('?', ' Shutting down gracefully...')
        lastActivityCheck && clearInterval(lastActivityCheck)
        await queue?.stop()
        await redisQueueForPluginJobs?.stop()
        await pubSub?.stop()
        actionsReloadJob && schedule.cancelJob(actionsReloadJob)
        pingJob && schedule.cancelJob(pingJob)
        pluginMetricsJob && schedule.cancelJob(pluginMetricsJob)
        statusReport.stopStatusReportSchedule()
        piscinaStatsJob && schedule.cancelJob(piscinaStatsJob)
        internalMetricsStatsJob && schedule.cancelJob(internalMetricsStatsJob)
        await jobQueueConsumer?.stop()
        await scheduleControl?.stopSchedule()
        await new Promise<void>((resolve, reject) =>
            !mmdbServer
                ? resolve()
                : mmdbServer.close((error) => {
                      if (error) {
                          reject(error)
                      } else {
                          status.info('?', 'Closed internal MMDB server!')
                          resolve()
                      }
                  })
        )
        if (piscina) {
            await stopPiscina(piscina)
        }
        await closeHub?.()
        status.info('?', 'Over and out!')
        // wait an extra second for any misc async task to finish
        await delay(1000)
    }

    for (const signal of ['SIGINT', 'SIGTERM', 'SIGHUP']) {
        process.on(signal, () => process.emit('beforeExit', 0))
    }

    process.on('beforeExit', async () => {
        // This makes async exit possible with the process waiting until jobs are closed
        await closeJobs()
        process.exit(0)
    })

    try {
        ;[hub, closeHub] = await createHub(serverConfig, null)

        const serverInstance: Partial<ServerInstance> & Pick<ServerInstance, 'hub'> = {
            hub,
        }

        if (!serverConfig.DISABLE_MMDB) {
            serverInstance.mmdb = (await prepareMmdb(serverInstance)) ?? undefined
            serverInstance.mmdbUpdateJob = schedule.scheduleJob(
                '0 */4 * * *',
                async () => await performMmdbStalenessCheck(serverInstance)
            )
            mmdbServer = await createMmdbServer(serverInstance)
            serverConfig.INTERNAL_MMDB_SERVER_PORT = (mmdbServer.address() as AddressInfo).port
            hub.INTERNAL_MMDB_SERVER_PORT = serverConfig.INTERNAL_MMDB_SERVER_PORT
        }

        piscina = makePiscina(serverConfig)

        scheduleControl = await startSchedule(hub, piscina)
        jobQueueConsumer = await startJobQueueConsumer(hub, piscina)

        const queues = await startQueues(hub, piscina)

        // `queue` refers to the ingestion queue. With Celery ingestion, we only
        // have one queue for plugin jobs and ingestion. With Kafka ingestion, we
        // use Kafka for events but still start Redis for plugin jobs.
        // Thus, if Kafka is disabled, we don't need to call anything on
        // redisQueueForPluginJobs, as that will also be the ingestion queue.
        queue = queues.ingestion
        redisQueueForPluginJobs = config.KAFKA_ENABLED ? queues.auxiliary : null
        piscina.on('drain', () => {
            void queue?.resume()
            void redisQueueForPluginJobs?.resume()
            void jobQueueConsumer?.resume()
        })

        // use one extra Redis connection for pub-sub
        pubSub = new PubSub(hub, {
            [hub.PLUGINS_RELOAD_PUBSUB_CHANNEL]: async () => {
                status.info('⚡', 'Reloading plugins!')
                await piscina?.broadcastTask({ task: 'reloadPlugins' })
                await scheduleControl?.reloadSchedule()
            },
            'reload-action': async (message) =>
                await piscina?.broadcastTask({ task: 'reloadAction', args: JSON.parse(message) }),
            'drop-action': async (message) =>
                await piscina?.broadcastTask({ task: 'dropAction', args: JSON.parse(message) }),
            'plugins-alert': async (message) =>
                await piscina?.run({ task: 'handleAlert', args: { alert: JSON.parse(message) } }),
        })

        await pubSub.start()

        if (hub.jobQueueManager) {
            const queueString = hub.jobQueueManager.getJobQueueTypesAsString()
            await hub!.db!.redisSet('@posthog-plugin-server/enabled-job-queues', queueString)
        }

        // every 5 minutes all ActionManager caches are reloaded for eventual consistency
        actionsReloadJob = schedule.scheduleJob('*/5 * * * *', async () => {
            await piscina?.broadcastTask({ task: 'reloadAllActions' })
        })
        // every 5 seconds set Redis keys @posthog-plugin-server/ping and @posthog-plugin-server/version
        pingJob = schedule.scheduleJob('*/5 * * * * *', async () => {
            await hub!.db!.redisSet('@posthog-plugin-server/ping', new Date().toISOString(), 60, {
                jsonSerialize: false,
            })
            await hub!.db!.redisSet('@posthog-plugin-server/version', version, undefined, { jsonSerialize: false })
        })
        // every 10 seconds sends stuff to StatsD
        piscinaStatsJob = schedule.scheduleJob('*/10 * * * * *', () => {
            if (piscina) {
                for (const [key, value] of Object.entries(getPiscinaStats(piscina))) {
                    hub!.statsd?.gauge(`piscina.${key}`, value)
                }
            }
        })

        // every minute flush internal metrics
        if (hub.internalMetrics) {
            internalMetricsStatsJob = schedule.scheduleJob('0 * * * * *', async () => {
                await hub!.internalMetrics?.flush(piscina!)
            })
        }

        pluginMetricsJob = schedule.scheduleJob('*/30 * * * *', async () => {
            await piscina!.broadcastTask({ task: 'sendPluginMetrics' })
        })

        if (serverConfig.STALENESS_RESTART_SECONDS > 0) {
            // check every 10 sec how long it has been since the last activity
            let lastFoundActivity: number
            lastActivityCheck = setInterval(() => {
                if (
                    hub?.lastActivity &&
                    new Date().valueOf() - hub?.lastActivity > serverConfig.STALENESS_RESTART_SECONDS * 1000 &&
                    lastFoundActivity !== hub?.lastActivity
                ) {
                    lastFoundActivity = hub?.lastActivity
                    const extra = {
                        instanceId: hub.instanceId.toString(),
                        lastActivity: hub.lastActivity ? new Date(hub.lastActivity).toISOString() : null,
                        lastActivityType: hub.lastActivityType,
                        piscina: piscina ? JSON.stringify(getPiscinaStats(piscina)) : null,
                    }
                    Sentry.captureMessage(
                        `Plugin Server has not ingested events for over ${serverConfig.STALENESS_RESTART_SECONDS} seconds! Rebooting.`,
                        {
                            extra,
                        }
                    )
                    console.log(
                        `Plugin Server has not ingested events for over ${serverConfig.STALENESS_RESTART_SECONDS} seconds! Rebooting.`,
                        extra
                    )
                    hub.statsd?.increment(`alerts.stale_plugin_server_restarted`)

                    killProcess()
                }
            }, Math.min(serverConfig.STALENESS_RESTART_SECONDS, 10000))
        }

        serverInstance.piscina = piscina
        serverInstance.queue = queue
        serverInstance.stop = closeJobs

        status.info('?', 'All systems go')

        hub.lastActivity = new Date().valueOf()
        hub.lastActivityType = 'serverStart'

        return serverInstance as ServerInstance
    } catch (error) {
        Sentry.captureException(error)
        status.error('?', 'Launchpad failure!', error)
        void Sentry.flush().catch(() => null) // Flush Sentry in the background
        await closeJobs()
        process.exit(1)
    }
}
Example #25
Source File: impl.ts    From kassette with MIT License 4 votes vote down vote up
////////////////////////////////////////////////////////////////////////////////
// Server
// FIXME 2018-09-25T14:00:24+02:00
// Multiple requests will be received in parallel, leading to a messed up output in the console...
// they should be queued and processed one by one to avoid that
////////////////////////////////////////////////////////////////////////////////

/** Spawns the server */
export async function spawnServer({ configuration, root }: ApplicationData): Promise<Server> {
  const server = createServer(async (request, response) => {
    const mock = new Mock({
      options: { root, userConfiguration: configuration },
      request: new Request(request, await readAll(request)),
      response: new Response(response),
    });

    logInfo({
      timestamp: true,
      message: CONF.messages.handlingRequest,
      data: `${request.method} ${request.url}`,
    });

    await configuration.hook.value({ mock, console: getConsole() });
    await mock.process();
  });

  const tlsManager = new TLSManager({
    tlsCAKeyPath: configuration.tlsCAKeyPath.value,
    tlsKeySize: configuration.tlsKeySize.value,
    root,
  });
  await tlsManager.init();

  const handleSocket = (socket: Socket) => {
    socket.once('data', async (data) => {
      socket.pause();
      socket.unshift(data);
      // cf https://github.com/mscdex/httpolyglot/issues/3#issuecomment-173680155
      // HTTPS:
      if (data.readUInt8(0) === 22) {
        socket = await tlsManager.process(socket);
        setConnectionProtocol(socket, 'https');
      } else {
        await Promise.resolve();
        setConnectionProtocol(socket, 'http');
      }
      server.emit('connection', socket);
      socket.resume();
    });
    socket.on('error', (exception) => logError({ message: CONF.messages.socketError, exception }));
  };

  server.on('connect', async (request: IncomingMessage, socket: Socket, data: Buffer) => {
    if (data.length > 0) {
      socket.unshift(data);
    }
    const api = new ProxyConnectAPI(request, configuration.proxyConnectMode.value, handleSocket);
    logInfo({
      timestamp: true,
      message: CONF.messages.handlingRequest,
      data: `${request.method} ${request.url}`,
    });
    await configuration.onProxyConnect.value(api);
    api.process();
  });

  // server that can receive both http and https connections
  const netServer = createNetServer(handleSocket);

  netServer.listen(configuration.port.value, configuration.hostname.value, function (this: Socket) {
    const port = (this.address() as AddressInfo).port;
    configuration.onListen.value({ port });

    logInfo({ message: CONF.messages.listening, data: port });
    logSeparator();
  });

  netServer.on('close', () => configuration.onExit.value());

  return netServer;
}