{"version":3,"sources":["../src/mock-artifactory-server.ts"],"sourcesContent":["import http from 'http';\nimport https from 'https';\nimport { URL } from 'url';\n\n/**\n * TEST-ONLY: Minimal in-memory mock of a subset of the JFrog Artifactory npm API surface\n * tailored to fhir-package-installer integration testing.\n *\n * Features:\n * 1. Bearer token auth (single fixed token) -> 401 if missing, 403 if invalid.\n * 2. Package metadata proxying to an upstream registry (defaults to packages.fhir.org).\n * 3. Tarball URL rewriting so downstream code exercises redirect logic.\n * 4. Tarball request simulation via 302 redirect to an upstream tarball base (defaults to packages.simplifier.net).\n *\n * NOT FOR PRODUCTION USE. Surface is intentionally small; breaking changes unlikely but\n * additions may occur in minor versions. Kept here so downstream libraries can reuse\n * without duplicating test utilities.\n */\nexport interface MockArtifactoryServerApi {\n  /** Starts listening. If port was 0, a random available port will be selected. */\n  start(): Promise<void>;\n  /** Gracefully stops the server. */\n  stop(): Promise<void>;\n  /** Base URL (http://localhost:port). */\n  getBaseUrl(): string;\n  /** Returns the static valid bearer token you must send under Authorization header. */\n  getValidToken(): string;\n}\n\ninterface PackageVersionMeta {\n  dist?: { tarball?: string };\n  [k: string]: unknown;\n}\n\nexport type MockArtifactoryServerOptions = {\n  /** Upstream npm-like registry base URL, e.g. https://packages.fhir.org or http://localhost:PORT */\n  upstreamRegistryUrl?: string;\n  /** Upstream tarball base URL used for 302 redirects, e.g. https://packages.simplifier.net or http://localhost:PORT */\n  upstreamTarballBaseUrl?: string;\n};\n\nclass InternalMockArtifactoryServer implements MockArtifactoryServerApi {\n  private server: http.Server;\n  private port: number;\n  private readonly validToken = 'test-token';\n  private upstreamRegistryUrl: string;\n  private upstreamTarballBaseUrl: string;\n\n  constructor(port: number, options: MockArtifactoryServerOptions = {}) {\n    this.port = port;\n    this.upstreamRegistryUrl = options.upstreamRegistryUrl ?? 'https://packages.fhir.org';\n    this.upstreamTarballBaseUrl = options.upstreamTarballBaseUrl ?? 'https://packages.simplifier.net';\n    this.server = this.createServer();\n  }\n\n  async start(): Promise<void> {\n    if (this.server.listening) return; // idempotent\n    await new Promise<void>((resolve, reject) => {\n      this.server.once('error', reject);\n      this.server.listen(this.port, () => resolve());\n    });\n    // If ephemeral port requested (0), update to the actual assigned port.\n    const addr = this.server.address();\n    if (typeof addr === 'object' && addr && 'port' in addr) this.port = addr.port;\n  }\n\n  async stop(): Promise<void> {\n    if (!this.server.listening) return; // idempotent\n    await new Promise<void>((resolve) => this.server.close(() => resolve()));\n  }\n\n  getBaseUrl(): string {\n    return `http://localhost:${this.port}`;\n  }\n\n  getValidToken(): string {\n    return this.validToken;\n  }\n\n  // ---- Internal implementation ----\n  private createServer(): http.Server {\n    return http.createServer(async (req, res) => {\n      // Basic CORS for convenience\n      res.setHeader('Access-Control-Allow-Origin', '*');\n      res.setHeader('Access-Control-Allow-Methods', 'GET, OPTIONS');\n      res.setHeader('Access-Control-Allow-Headers', 'Content-Type, Authorization');\n      if (req.method === 'OPTIONS') {\n        res.writeHead(200); res.end(); return;\n      }\n\n      // Auth checks\n      const authHeader = req.headers.authorization;\n      if (!authHeader || !authHeader.startsWith('Bearer ')) {\n        this.json(res, 401, { error: 'Unauthorized' });\n        return;\n      }\n      const token = authHeader.substring('Bearer '.length);\n      if (token !== this.validToken) {\n        this.json(res, 403, { error: 'Forbidden' });\n        return;\n      }\n\n      const url = new URL(req.url || '/', this.getBaseUrl());\n\n      try {\n        if (url.pathname.includes('/-/')) {\n          this.handleTarballRedirect(res, url);\n          return;\n        }\n        if (url.pathname.includes('/artifactory/api/npm/')) {\n          await this.handleArtifactoryMetadata(res, url);\n          return;\n        }\n        if (url.pathname.endsWith('/')) {\n          await this.handleSimplifierMetadata(res, url);\n          return;\n        }\n        this.json(res, 404, { error: 'Not found' });\n      } catch {\n        this.json(res, 500, { error: 'Internal server error' });\n      }\n    });\n  }\n\n  private async handleArtifactoryMetadata(res: http.ServerResponse, url: URL) {\n    // /artifactory/api/npm/<repo>/<package>/\n    const segments = url.pathname.split('/').filter(Boolean);\n    const packageName = segments.at(-1) || segments.at(-2);\n    if (!packageName) { this.json(res, 404, { error: 'Package not found' }); return; }\n    await this.fetchAndRewrite(packageName, res);\n  }\n\n  private async handleSimplifierMetadata(res: http.ServerResponse, url: URL) {\n    // /hl7.fhir.uv.sdc/ style path\n    const packageName = url.pathname.split('/').filter(Boolean)[0];\n    if (!packageName) { this.json(res, 404, { error: 'Package not found' }); return; }\n    await this.fetchAndRewrite(packageName, res);\n  }\n\n  private handleTarballRedirect(res: http.ServerResponse, url: URL) {\n    const parts = url.pathname.split('/').filter(Boolean);\n    // pattern: <package>/-/<file.tgz>\n    const tgzFile = parts.at(-1);\n    const packageName = parts.at(-3); // <package>/-/<file>\n    if (!tgzFile || !packageName) { this.json(res, 404, { error: 'Tarball not found' }); return; }\n    const base = this.upstreamTarballBaseUrl.replace(/\\/+$/, '');\n    const redirectUrl = `${base}/${packageName}/-/${tgzFile}`;\n    res.writeHead(302, { Location: redirectUrl, 'Content-Type': 'application/json' });\n    res.end(JSON.stringify({ message: 'Redirecting to Simplifier' }));\n  }\n\n  private async fetchAndRewrite(packageName: string, res: http.ServerResponse) {\n    try {\n      const base = this.upstreamRegistryUrl.replace(/\\/+$/, '');\n      const sourceUrl = `${base}/${packageName}/`;\n      const { data } = await this.fetchRaw(sourceUrl);\n      const modified = this.rewriteTarballs(data);\n      this.json(res, 200, modified);\n    } catch {\n      this.json(res, 404, { error: 'Package not found' });\n    }\n  }\n\n  private fetchRaw(url: string): Promise<{ statusCode: number; data: string }> {\n    return new Promise((resolve, reject) => {\n      const parsed = new URL(url);\n      const client = parsed.protocol === 'http:' ? http : https;\n      client.get(url, (resp) => {\n        let acc = '';\n        resp.on('data', (chunk) => acc += chunk);\n        resp.on('end', () => resolve({ statusCode: resp.statusCode || 0, data: acc }));\n      }).on('error', reject);\n    });\n  }\n\n  private rewriteTarballs(raw: string): unknown {\n    try {\n      const json = JSON.parse(raw);\n      if (json.versions) {\n        for (const v of Object.values(json.versions) as PackageVersionMeta[]) {\n          if (v.dist?.tarball) {\n            const original = new URL(v.dist.tarball);\n            const parts = original.pathname.split('/').filter(Boolean); // <package>/-/<file>\n            const packageName = parts[0];\n            const tgzFile = parts.at(-1);\n            if (packageName && tgzFile) {\n              v.dist.tarball = `${this.getBaseUrl()}/${packageName}/-/${tgzFile}`;\n            }\n          }\n        }\n      }\n      return json;\n    } catch {\n      return raw; // fall back to raw string\n    }\n  }\n\n  private json(res: http.ServerResponse, status: number, body: unknown) {\n    res.writeHead(status, { 'Content-Type': 'application/json' });\n    res.end(typeof body === 'string' ? body : JSON.stringify(body));\n  }\n}\n\n/** Factory to create a new mock Artifactory server instance.\n * @param port Optional port. Pass 0 for ephemeral.\n */\nexport function createMockArtifactoryServer(\n  port: number = 3333,\n  options: MockArtifactoryServerOptions = {}\n): MockArtifactoryServerApi {\n  return new InternalMockArtifactoryServer(port, options);\n}\n\nexport const MOCK_ARTIFACTORY_VALID_TOKEN = 'test-token';\n"],"mappings":"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","names":[]}