Ctrl K
ComunidadGuías…

src/services/embedding.test.ts

Rama main · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
import assert from 'node:assert/strict';
import test, { type TestContext } from 'node:test';
import {
  dot,
  embedViaApi,
  EmbeddingRequestError,
  normalize,
  type EmbeddingRequestErrorKind,
} from './embedding';

const CREDENTIAL = {
  base_url: 'https://private-embedding.example.invalid/v1',
  api_key: 'api-key-must-not-leak',
};
const RESPONSE_SECRET = 'upstream-body-must-not-leak';

function useFetch(
  t: TestContext,
  stub: (...args: Parameters<typeof fetch>) => Promise<Response>,
): void {
  const original = globalThis.fetch;
  globalThis.fetch = stub as typeof fetch;
  t.after(() => { globalThis.fetch = original; });
}

function jsonResponse(value: unknown, status = 200): Response {
  return new Response(JSON.stringify(value), {
    status,
    headers: { 'content-type': 'application/json' },
  });
}

function assertApprox(actual: number, expected: number): void {
  assert.ok(Math.abs(actual - expected) < 1e-6, `${actual} should be close to ${expected}`);
}

function assertSafeError(error: unknown, kind: EmbeddingRequestErrorKind): asserts error is EmbeddingRequestError {
  assert.ok(error instanceof EmbeddingRequestError);
  assert.equal(error.kind, kind);
  assert.doesNotMatch(error.message, /private-embedding|api-key-must-not-leak|upstream-body-must-not-leak/);
}

test('embedding transport normalizes vectors and exposes the shared dot product', () => {
  const first = normalize(Float32Array.from([3, 4]));
  const second = normalize(Float32Array.from([4, 3]));
  assertApprox(first[0]!, 0.6);
  assertApprox(first[1]!, 0.8);
  assertApprox(dot(first, second), 0.96);
});

test('embedding transport batches requests, restores response order, and keeps no-timeout calls compatible', async (t) => {
  const inputs: string[][] = [];
  useFetch(t, async (_input, init) => {
    assert.equal(init?.signal, undefined);
    const body = JSON.parse(String(init?.body)) as { input: string[]; dimensions?: number };
    inputs.push(body.input);
    assert.equal(body.dimensions, 4);
    return jsonResponse({
      data: body.input.map((_text, index) => ({ index, embedding: [index + 1, 1, 0, 0] })).reverse(),
    });
  });

  const texts = Array.from({ length: 11 }, (_unused, index) => `text-${index}`);
  const vectors = await embedViaApi(CREDENTIAL, 'embedding-model', 4, texts);

  assert.deepEqual(inputs.map((batch) => batch.length), [10, 1]);
  assert.deepEqual(inputs.flat(), texts);
  assert.equal(vectors.length, texts.length);
  assertApprox(vectors[0]![0]!, Math.SQRT1_2);
  assertApprox(vectors[1]![0]!, 2 / Math.sqrt(5));
});

test('embedding request timeout is reset for every HTTP batch instead of covering a whole rebuild', async (t) => {
  const signals: AbortSignal[] = [];
  useFetch(t, async (_input, init) => {
    const signal = init?.signal;
    assert.ok(signal);
    assert.equal(signal.aborted, false);
    signals.push(signal);

    await new Promise<void>((resolve, reject) => {
      const timer = setTimeout(() => {
        signal.removeEventListener('abort', onAbort);
        resolve();
      }, 90);
      const onAbort = (): void => {
        clearTimeout(timer);
        reject(new DOMException('request aborted', 'AbortError'));
      };
      signal.addEventListener('abort', onAbort, { once: true });
    });

    const body = JSON.parse(String(init?.body)) as { input: string[] };
    return jsonResponse({ data: body.input.map((_text, index) => ({ index, embedding: [1, 0] })) });
  });

  const startedAt = Date.now();
  const vectors = await embedViaApi(
    CREDENTIAL,
    'embedding-model',
    2,
    Array.from({ length: 11 }, (_unused, index) => `text-${index}`),
    { requestTimeoutMs: 150 },
  );

  assert.equal(vectors.length, 11);
  assert.equal(signals.length, 2);
  assert.notEqual(signals[0], signals[1]);
  assert.ok(Date.now() - startedAt >= 150, '总耗时应超过单批截止时间,但两批仍都成功');
});

test('embedding transport aborts a stalled request and returns a sanitized timeout error', async (t) => {
  useFetch(t, async (_input, init) => {
    const signal = init?.signal;
    assert.ok(signal);
    return await new Promise<Response>((_resolve, reject) => {
      const onAbort = (): void => reject(new Error(`aborted ${CREDENTIAL.base_url} ${CREDENTIAL.api_key}`));
      signal.addEventListener('abort', onAbort, { once: true });
    });
  });

  await assert.rejects(
    () => embedViaApi(CREDENTIAL, 'embedding-model', 2, ['hello'], { requestTimeoutMs: 20 }),
    (error) => {
      assertSafeError(error, 'timeout');
      assert.equal(error.message, 'embedding request timed out');
      return true;
    },
  );
});

test('embedding transport returns a typed HTTP error without reading or leaking the response body', async (t) => {
  useFetch(t, async () => new Response(RESPONSE_SECRET, { status: 503 }));

  await assert.rejects(
    () => embedViaApi(CREDENTIAL, 'embedding-model', 2, ['hello']),
    (error) => {
      assertSafeError(error, 'http');
      assert.equal(error.status, 503);
      assert.equal(error.message, 'embedding API returned HTTP 503');
      return true;
    },
  );
});

test('embedding transport returns a typed response error when vector count is wrong', async (t) => {
  useFetch(t, async () => jsonResponse({ data: [] }));

  await assert.rejects(
    () => embedViaApi(CREDENTIAL, 'embedding-model', 2, ['hello']),
    (error) => {
      assertSafeError(error, 'response');
      assert.equal(error.message, 'embedding API returned an invalid response');
      return true;
    },
  );
});

test('embedding transport rejects vectors whose length differs from the requested dimension', async (t) => {
  useFetch(t, async () => jsonResponse({ data: [{ index: 0, embedding: [1, 0, 0] }] }));

  await assert.rejects(
    () => embedViaApi(CREDENTIAL, 'embedding-model', 2, ['hello']),
    (error) => {
      assertSafeError(error, 'response');
      assert.equal(error.message, 'embedding API returned an invalid response');
      return true;
    },
  );
});

test('embedding transport sanitizes unexpected network failures', async (t) => {
  useFetch(t, async () => {
    throw new Error(`connect failed: ${CREDENTIAL.base_url} ${CREDENTIAL.api_key} ${RESPONSE_SECRET}`);
  });

  await assert.rejects(
    () => embedViaApi(CREDENTIAL, 'embedding-model', 2, ['hello']),
    (error) => {
      assertSafeError(error, 'network');
      assert.equal(error.message, 'embedding request failed');
      return true;
    },
  );
});