v4.3.21: 테스트 인프라 복구 — npm test 실행 가능하게 + 회귀 테스트 30개
tests/ 디렉터리가 비어 있었고 package.json의 두 스크립트가 삭제된 파일을 가리킨 채 방치돼 있었음: - test → tests/test-v2.ts (존재하지 않음) - gateway → src/gateway/server-v2.ts (v4.3.5에서 삭제됨) 즉 npm test가 몇 달간 실행 자체가 불가능한 상태였고, 그 대가로 프롬프트 게이트 정규식을 고칠 때마다 node -e 임시 스크립트를 손으로 짜고 버리는 일이 반복됐음 (2026-07-29 하루에만 5회). 회귀 방지는 하나도 남지 않았음. - Node 22 내장 러너(node:test) + tsx 사용, 테스트 프레임워크 의존성 없음 - tests/prompt-gates.test.ts (25) — 모델 동작을 강제/억제하는 정규식 게이트 전반 - tests/usage-log.test.ts (5) — Ollama 외 provider의 토큰 집계 단일 지점 - tests/README.md — 무엇을 왜 테스트하는지, 케이스 작성 규칙, 다음 확장 대상 - 전체 30개 통과, 0.6초 부수 성과 — 테스트가 실제 버그를 찾음: 케이스를 쓰면서 프로덕션 원문을 한 단어(GPU) 줄여 썼더니 실패했고, 그게 진짜 구멍이었음. "토큰 처리 속도 손실이 약 15%~25%"처럼 하드웨어 명사가 없는 처리량 날조는 SPEC_CONTEXT_KEYWORD에 걸리지 않아 그대로 통과하고 있었음 — 원문에 우연히 들어있던 "GPU" 덕에 잡히던 것. 토큰/대역폭/추론/처리속도를 키워드에 추가해 막고, 그 케이스를 테스트로 고정함(강수확률·할인율 오탐 없음 확인). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,50 @@
|
||||
/**
|
||||
* usage-log.test.ts
|
||||
*
|
||||
* parseOpenAiUsage is the single point where every non-Ollama provider's token counts get
|
||||
* picked up (openai-compat serves Gemini/LM Studio/OpenAI, openai-codex serves Codex). It
|
||||
* returning null silently means a provider vanishes from the cost-comparison log entirely —
|
||||
* which is exactly the bug this module was extracted to fix on 2026-07-29, and the kind that
|
||||
* produces no error, just a quietly wrong conclusion months later.
|
||||
*/
|
||||
|
||||
import { test, describe } from 'node:test';
|
||||
import assert from 'node:assert/strict';
|
||||
import { parseOpenAiUsage } from '../src/providers/usage-log';
|
||||
|
||||
describe('parseOpenAiUsage', () => {
|
||||
test('표준 OpenAI usage 필드를 읽는다', () => {
|
||||
assert.deepEqual(
|
||||
parseOpenAiUsage({ prompt_tokens: 137, completion_tokens: 42 }),
|
||||
{ promptTokens: 137, completionTokens: 42 },
|
||||
);
|
||||
});
|
||||
|
||||
test('camelCase 변형도 받는다', () => {
|
||||
assert.deepEqual(
|
||||
parseOpenAiUsage({ promptTokens: 10, completionTokens: 20 }),
|
||||
{ promptTokens: 10, completionTokens: 20 },
|
||||
);
|
||||
});
|
||||
|
||||
test('한쪽만 있어도 기록한다 — 절반이라도 남기는 게 0건보다 낫다', () => {
|
||||
assert.deepEqual(parseOpenAiUsage({ prompt_tokens: 500 }), { promptTokens: 500, completionTokens: 0 });
|
||||
assert.deepEqual(parseOpenAiUsage({ completion_tokens: 7 }), { promptTokens: 0, completionTokens: 7 });
|
||||
});
|
||||
|
||||
test('usage가 없거나 값이 없으면 null — 0/0짜리 가짜 행을 남기지 않는다', () => {
|
||||
assert.equal(parseOpenAiUsage(undefined), null);
|
||||
assert.equal(parseOpenAiUsage(null), null);
|
||||
assert.equal(parseOpenAiUsage({}), null);
|
||||
assert.equal(parseOpenAiUsage({ prompt_tokens: 0, completion_tokens: 0 }), null);
|
||||
assert.equal(parseOpenAiUsage('nope' as any), null);
|
||||
});
|
||||
|
||||
test('숫자가 아닌 값은 0으로 떨어뜨린다 (NaN이 로그에 새지 않게)', () => {
|
||||
assert.equal(parseOpenAiUsage({ prompt_tokens: 'abc', completion_tokens: 'def' }), null);
|
||||
assert.deepEqual(
|
||||
parseOpenAiUsage({ prompt_tokens: 'abc', completion_tokens: 5 }),
|
||||
{ promptTokens: 0, completionTokens: 5 },
|
||||
);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user