| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673 |
- import { describe, it, expect } from "vitest";
- // 由于 extractUsageMetrics 是内部函数,需要通过 parseUsageFromResponseText 间接测试
- // 或者将其导出用于测试
- // 这里我们通过构造 JSON 响应来测试 parseUsageFromResponseText
- import { parseUsageFromResponseText } from "@/app/v1/_lib/proxy/response-handler";
- describe("extractUsageMetrics", () => {
- describe("基本 token 提取", () => {
- it("应正确提取 input_tokens 和 output_tokens", () => {
- const response = JSON.stringify({
- usage: {
- input_tokens: 1000,
- output_tokens: 500,
- },
- });
- const result = parseUsageFromResponseText(response, "claude");
- expect(result.usageMetrics).not.toBeNull();
- expect(result.usageMetrics?.input_tokens).toBe(1000);
- expect(result.usageMetrics?.output_tokens).toBe(500);
- });
- it("空值或非对象应返回 null", () => {
- expect(parseUsageFromResponseText("", "claude").usageMetrics).toBeNull();
- expect(parseUsageFromResponseText("null", "claude").usageMetrics).toBeNull();
- expect(parseUsageFromResponseText('"string"', "claude").usageMetrics).toBeNull();
- });
- });
- describe("Claude 嵌套格式 (cache_creation.ephemeral_*)", () => {
- it("应从 cache_creation 嵌套对象提取 5m 和 1h token", () => {
- const response = JSON.stringify({
- usage: {
- input_tokens: 1000,
- output_tokens: 500,
- cache_creation_input_tokens: 800,
- cache_creation: {
- ephemeral_5m_input_tokens: 300,
- ephemeral_1h_input_tokens: 500,
- },
- cache_read_input_tokens: 200,
- },
- });
- const result = parseUsageFromResponseText(response, "claude");
- expect(result.usageMetrics).not.toBeNull();
- expect(result.usageMetrics?.cache_creation_input_tokens).toBe(800);
- expect(result.usageMetrics?.cache_creation_5m_input_tokens).toBe(300);
- expect(result.usageMetrics?.cache_creation_1h_input_tokens).toBe(500);
- expect(result.usageMetrics?.cache_read_input_tokens).toBe(200);
- expect(result.usageMetrics?.cache_ttl).toBe("mixed");
- });
- it("只有 5m 时应推断 cache_ttl 为 5m", () => {
- const response = JSON.stringify({
- usage: {
- cache_creation_input_tokens: 300,
- cache_creation: {
- ephemeral_5m_input_tokens: 300,
- },
- },
- });
- const result = parseUsageFromResponseText(response, "claude");
- expect(result.usageMetrics?.cache_creation_5m_input_tokens).toBe(300);
- expect(result.usageMetrics?.cache_creation_1h_input_tokens).toBeUndefined();
- expect(result.usageMetrics?.cache_ttl).toBe("5m");
- });
- it("只有 1h 时应推断 cache_ttl 为 1h", () => {
- const response = JSON.stringify({
- usage: {
- cache_creation_input_tokens: 500,
- cache_creation: {
- ephemeral_1h_input_tokens: 500,
- },
- },
- });
- const result = parseUsageFromResponseText(response, "claude");
- expect(result.usageMetrics?.cache_creation_1h_input_tokens).toBe(500);
- expect(result.usageMetrics?.cache_creation_5m_input_tokens).toBeUndefined();
- expect(result.usageMetrics?.cache_ttl).toBe("1h");
- });
- });
- describe("旧 relay 格式 (claude_cache_creation_*)", () => {
- it("应从旧 relay 字段提取 5m 和 1h token", () => {
- const response = JSON.stringify({
- usage: {
- input_tokens: 1000,
- output_tokens: 500,
- cache_creation_input_tokens: 800,
- claude_cache_creation_5_m_tokens: 300,
- claude_cache_creation_1_h_tokens: 500,
- cache_read_input_tokens: 200,
- },
- });
- const result = parseUsageFromResponseText(response, "claude");
- expect(result.usageMetrics?.cache_creation_5m_input_tokens).toBe(300);
- expect(result.usageMetrics?.cache_creation_1h_input_tokens).toBe(500);
- expect(result.usageMetrics?.cache_ttl).toBe("mixed");
- });
- it("嵌套格式应优先于旧 relay 格式", () => {
- const response = JSON.stringify({
- usage: {
- cache_creation: {
- ephemeral_5m_input_tokens: 100,
- ephemeral_1h_input_tokens: 200,
- },
- claude_cache_creation_5_m_tokens: 999,
- claude_cache_creation_1_h_tokens: 888,
- },
- });
- const result = parseUsageFromResponseText(response, "claude");
- // 嵌套格式优先
- expect(result.usageMetrics?.cache_creation_5m_input_tokens).toBe(100);
- expect(result.usageMetrics?.cache_creation_1h_input_tokens).toBe(200);
- });
- });
- describe("顶层扁平格式 (cache_creation_5m_input_tokens)", () => {
- it("应从顶层扁平字段提取 5m 和 1h token", () => {
- const response = JSON.stringify({
- usage: {
- input_tokens: 1000,
- output_tokens: 500,
- cache_creation_input_tokens: 800,
- cache_creation_5m_input_tokens: 300,
- cache_creation_1h_input_tokens: 500,
- cache_read_input_tokens: 200,
- },
- });
- const result = parseUsageFromResponseText(response, "claude");
- expect(result.usageMetrics?.cache_creation_input_tokens).toBe(800);
- expect(result.usageMetrics?.cache_creation_5m_input_tokens).toBe(300);
- expect(result.usageMetrics?.cache_creation_1h_input_tokens).toBe(500);
- expect(result.usageMetrics?.cache_read_input_tokens).toBe(200);
- expect(result.usageMetrics?.cache_ttl).toBe("mixed");
- });
- it("只有顶层 5m 时应正确提取并推断 TTL", () => {
- const response = JSON.stringify({
- usage: {
- cache_creation_input_tokens: 300,
- cache_creation_5m_input_tokens: 300,
- },
- });
- const result = parseUsageFromResponseText(response, "claude");
- expect(result.usageMetrics?.cache_creation_5m_input_tokens).toBe(300);
- expect(result.usageMetrics?.cache_ttl).toBe("5m");
- });
- it("只有顶层 1h 时应正确提取并推断 TTL", () => {
- const response = JSON.stringify({
- usage: {
- cache_creation_input_tokens: 500,
- cache_creation_1h_input_tokens: 500,
- },
- });
- const result = parseUsageFromResponseText(response, "claude");
- expect(result.usageMetrics?.cache_creation_1h_input_tokens).toBe(500);
- expect(result.usageMetrics?.cache_ttl).toBe("1h");
- });
- it("嵌套格式应优先于顶层扁平格式", () => {
- const response = JSON.stringify({
- usage: {
- cache_creation: {
- ephemeral_5m_input_tokens: 100,
- ephemeral_1h_input_tokens: 200,
- },
- cache_creation_5m_input_tokens: 999,
- cache_creation_1h_input_tokens: 888,
- },
- });
- const result = parseUsageFromResponseText(response, "claude");
- // 嵌套格式优先
- expect(result.usageMetrics?.cache_creation_5m_input_tokens).toBe(100);
- expect(result.usageMetrics?.cache_creation_1h_input_tokens).toBe(200);
- });
- it("顶层扁平格式应优先于旧 relay 格式", () => {
- const response = JSON.stringify({
- usage: {
- cache_creation_5m_input_tokens: 300,
- cache_creation_1h_input_tokens: 500,
- claude_cache_creation_5_m_tokens: 999,
- claude_cache_creation_1_h_tokens: 888,
- },
- });
- const result = parseUsageFromResponseText(response, "claude");
- // 顶层扁平格式优先于旧 relay 格式
- expect(result.usageMetrics?.cache_creation_5m_input_tokens).toBe(300);
- expect(result.usageMetrics?.cache_creation_1h_input_tokens).toBe(500);
- });
- it("三种格式同时存在时应按优先级提取", () => {
- const response = JSON.stringify({
- usage: {
- cache_creation: {
- ephemeral_5m_input_tokens: 100,
- ephemeral_1h_input_tokens: 200,
- },
- cache_creation_5m_input_tokens: 300,
- cache_creation_1h_input_tokens: 400,
- claude_cache_creation_5_m_tokens: 500,
- claude_cache_creation_1_h_tokens: 600,
- },
- });
- const result = parseUsageFromResponseText(response, "claude");
- // 嵌套格式最优先
- expect(result.usageMetrics?.cache_creation_5m_input_tokens).toBe(100);
- expect(result.usageMetrics?.cache_creation_1h_input_tokens).toBe(200);
- expect(result.usageMetrics?.cache_ttl).toBe("mixed");
- });
- });
- describe("cache_creation_input_tokens 自动计算", () => {
- it("当 cache_creation_input_tokens 缺失时应自动计算总量", () => {
- const response = JSON.stringify({
- usage: {
- cache_creation: {
- ephemeral_5m_input_tokens: 300,
- ephemeral_1h_input_tokens: 500,
- },
- },
- });
- const result = parseUsageFromResponseText(response, "claude");
- expect(result.usageMetrics?.cache_creation_input_tokens).toBe(800);
- });
- it("顶层扁平格式缺失 cache_creation_input_tokens 时应自动计算总量", () => {
- const response = JSON.stringify({
- usage: {
- cache_creation_5m_input_tokens: 400,
- cache_creation_1h_input_tokens: 600,
- },
- });
- const result = parseUsageFromResponseText(response, "claude");
- expect(result.usageMetrics?.cache_creation_input_tokens).toBe(1000);
- expect(result.usageMetrics?.cache_creation_5m_input_tokens).toBe(400);
- expect(result.usageMetrics?.cache_creation_1h_input_tokens).toBe(600);
- });
- it("混合回退:嵌套缺失某字段时顶层扁平补齐", () => {
- const response = JSON.stringify({
- usage: {
- cache_creation: {
- ephemeral_5m_input_tokens: 200,
- // 缺失 ephemeral_1h_input_tokens
- },
- cache_creation_1h_input_tokens: 300, // 顶层扁平补齐
- },
- });
- const result = parseUsageFromResponseText(response, "claude");
- // 5m 来自嵌套,1h 来自顶层扁平
- expect(result.usageMetrics?.cache_creation_5m_input_tokens).toBe(200);
- expect(result.usageMetrics?.cache_creation_1h_input_tokens).toBe(300);
- expect(result.usageMetrics?.cache_creation_input_tokens).toBe(500);
- expect(result.usageMetrics?.cache_ttl).toBe("mixed");
- });
- it("当 cache_creation_input_tokens 存在时不应覆盖", () => {
- const response = JSON.stringify({
- usage: {
- cache_creation_input_tokens: 1000,
- cache_creation: {
- ephemeral_5m_input_tokens: 300,
- ephemeral_1h_input_tokens: 500,
- },
- },
- });
- const result = parseUsageFromResponseText(response, "claude");
- // 保留原值
- expect(result.usageMetrics?.cache_creation_input_tokens).toBe(1000);
- });
- });
- describe("Gemini 格式支持", () => {
- it("应正确提取 Gemini usage 字段", () => {
- const response = JSON.stringify({
- usageMetadata: {
- promptTokenCount: 1000,
- candidatesTokenCount: 500,
- cachedContentTokenCount: 200,
- },
- });
- const result = parseUsageFromResponseText(response, "gemini");
- expect(result.usageMetrics).not.toBeNull();
- // input_tokens = promptTokenCount - cachedContentTokenCount
- expect(result.usageMetrics?.input_tokens).toBe(800);
- expect(result.usageMetrics?.output_tokens).toBe(500);
- expect(result.usageMetrics?.cache_read_input_tokens).toBe(200);
- });
- it("应正确处理 Gemini thoughtsTokenCount", () => {
- const response = JSON.stringify({
- usageMetadata: {
- promptTokenCount: 1000,
- candidatesTokenCount: 500,
- thoughtsTokenCount: 100,
- },
- });
- const result = parseUsageFromResponseText(response, "gemini");
- // output_tokens = candidatesTokenCount + thoughtsTokenCount
- expect(result.usageMetrics?.output_tokens).toBe(600);
- });
- it("应从 candidatesTokensDetails 提取 IMAGE modality tokens", () => {
- const response = JSON.stringify({
- usageMetadata: {
- promptTokenCount: 326,
- candidatesTokenCount: 2340,
- candidatesTokensDetails: [
- { modality: "IMAGE", tokenCount: 2000 },
- { modality: "TEXT", tokenCount: 340 },
- ],
- },
- });
- const result = parseUsageFromResponseText(response, "gemini");
- expect(result.usageMetrics?.output_image_tokens).toBe(2000);
- expect(result.usageMetrics?.output_tokens).toBe(340);
- });
- it("应从 promptTokensDetails 提取 IMAGE modality tokens", () => {
- const response = JSON.stringify({
- usageMetadata: {
- promptTokenCount: 886,
- candidatesTokenCount: 500,
- promptTokensDetails: [
- { modality: "TEXT", tokenCount: 326 },
- { modality: "IMAGE", tokenCount: 560 },
- ],
- },
- });
- const result = parseUsageFromResponseText(response, "gemini");
- expect(result.usageMetrics?.input_image_tokens).toBe(560);
- expect(result.usageMetrics?.input_tokens).toBe(326);
- });
- it("应正确解析混合输入输出的完整 usage", () => {
- const response = JSON.stringify({
- usageMetadata: {
- promptTokenCount: 357,
- candidatesTokenCount: 2100,
- totalTokenCount: 2580,
- promptTokensDetails: [
- { modality: "TEXT", tokenCount: 99 },
- { modality: "IMAGE", tokenCount: 258 },
- ],
- candidatesTokensDetails: [{ modality: "IMAGE", tokenCount: 2000 }],
- thoughtsTokenCount: 123,
- },
- });
- const result = parseUsageFromResponseText(response, "gemini");
- expect(result.usageMetrics?.input_tokens).toBe(99);
- expect(result.usageMetrics?.input_image_tokens).toBe(258);
- // output_tokens = (candidatesTokenCount - IMAGE详情) + thoughtsTokenCount
- // = (2100 - 2000) + 123 = 223
- expect(result.usageMetrics?.output_tokens).toBe(223);
- expect(result.usageMetrics?.output_image_tokens).toBe(2000);
- });
- it("应处理只有 IMAGE modality 的 candidatesTokensDetails", () => {
- const response = JSON.stringify({
- usageMetadata: {
- promptTokenCount: 100,
- candidatesTokenCount: 2000,
- candidatesTokensDetails: [{ modality: "IMAGE", tokenCount: 2000 }],
- },
- });
- const result = parseUsageFromResponseText(response, "gemini");
- expect(result.usageMetrics?.output_image_tokens).toBe(2000);
- // candidatesTokenCount = 2000, IMAGE = 2000, 未分类 = 0
- expect(result.usageMetrics?.output_tokens).toBe(0);
- });
- it("应计算 candidatesTokenCount 与 details 的差值作为未分类 TEXT", () => {
- const response = JSON.stringify({
- usageMetadata: {
- promptTokenCount: 326,
- candidatesTokenCount: 2340,
- candidatesTokensDetails: [{ modality: "IMAGE", tokenCount: 2000 }],
- thoughtsTokenCount: 337,
- },
- });
- const result = parseUsageFromResponseText(response, "gemini");
- // 未分类 = 2340 - 2000 = 340
- // output_tokens = 340 + 337 (thoughts) = 677
- expect(result.usageMetrics?.output_tokens).toBe(677);
- expect(result.usageMetrics?.output_image_tokens).toBe(2000);
- });
- it("应处理缺失 candidatesTokensDetails 的情况(向后兼容)", () => {
- const response = JSON.stringify({
- usageMetadata: {
- promptTokenCount: 1000,
- candidatesTokenCount: 500,
- },
- });
- const result = parseUsageFromResponseText(response, "gemini");
- expect(result.usageMetrics?.output_tokens).toBe(500);
- expect(result.usageMetrics?.output_image_tokens).toBeUndefined();
- expect(result.usageMetrics?.input_image_tokens).toBeUndefined();
- });
- it("应处理空的 candidatesTokensDetails 数组", () => {
- const response = JSON.stringify({
- usageMetadata: {
- promptTokenCount: 1000,
- candidatesTokenCount: 500,
- candidatesTokensDetails: [],
- },
- });
- const result = parseUsageFromResponseText(response, "gemini");
- expect(result.usageMetrics?.output_tokens).toBe(500);
- expect(result.usageMetrics?.output_image_tokens).toBeUndefined();
- });
- it("应处理 candidatesTokensDetails 中无效 tokenCount 的情况", () => {
- const response = JSON.stringify({
- usageMetadata: {
- promptTokenCount: 1000,
- candidatesTokenCount: 500,
- candidatesTokensDetails: [
- { modality: "TEXT" },
- { modality: "IMAGE", tokenCount: null },
- { modality: "TEXT", tokenCount: -1 },
- ],
- },
- });
- const result = parseUsageFromResponseText(response, "gemini");
- // 无效数据不应覆盖原始 candidatesTokenCount
- expect(result.usageMetrics?.output_tokens).toBe(500);
- expect(result.usageMetrics?.output_image_tokens).toBeUndefined();
- });
- it("应处理 modality 大小写变体", () => {
- const response = JSON.stringify({
- usageMetadata: {
- promptTokenCount: 100,
- candidatesTokenCount: 2340,
- candidatesTokensDetails: [
- { modality: "image", tokenCount: 2000 },
- { modality: "Image", tokenCount: 100 },
- { modality: "TEXT", tokenCount: 240 },
- ],
- },
- });
- const result = parseUsageFromResponseText(response, "gemini");
- expect(result.usageMetrics?.output_image_tokens).toBe(2100);
- expect(result.usageMetrics?.output_tokens).toBe(240);
- });
- });
- describe("OpenAI Response API 格式", () => {
- it("应从 input_tokens_details.cached_tokens 提取缓存读取", () => {
- const response = JSON.stringify({
- usage: {
- input_tokens: 1000,
- output_tokens: 500,
- input_tokens_details: {
- cached_tokens: 200,
- },
- },
- });
- const result = parseUsageFromResponseText(response, "openai");
- expect(result.usageMetrics?.cache_read_input_tokens).toBe(200);
- });
- it("顶层 cache_read_input_tokens 应优先于嵌套格式", () => {
- const response = JSON.stringify({
- usage: {
- input_tokens: 1000,
- cache_read_input_tokens: 300,
- input_tokens_details: {
- cached_tokens: 200,
- },
- },
- });
- const result = parseUsageFromResponseText(response, "openai");
- // 顶层优先
- expect(result.usageMetrics?.cache_read_input_tokens).toBe(300);
- });
- });
- describe("SSE 流式响应解析", () => {
- it("应正确合并 message_start 和 message_delta 的 usage", () => {
- // 模拟 Claude SSE 流式响应
- const sseResponse = [
- "event: message_start",
- 'data: {"type":"message_start","message":{"usage":{"input_tokens":1000,"cache_creation_input_tokens":500,"cache_creation":{"ephemeral_5m_input_tokens":200,"ephemeral_1h_input_tokens":300},"cache_read_input_tokens":100}}}',
- "",
- "event: message_delta",
- 'data: {"type":"message_delta","usage":{"output_tokens":800}}',
- "",
- ].join("\n");
- const result = parseUsageFromResponseText(sseResponse, "claude");
- expect(result.usageMetrics).not.toBeNull();
- expect(result.usageMetrics?.input_tokens).toBe(1000);
- expect(result.usageMetrics?.output_tokens).toBe(800);
- expect(result.usageMetrics?.cache_creation_input_tokens).toBe(500);
- expect(result.usageMetrics?.cache_creation_5m_input_tokens).toBe(200);
- expect(result.usageMetrics?.cache_creation_1h_input_tokens).toBe(300);
- expect(result.usageMetrics?.cache_read_input_tokens).toBe(100);
- });
- it("message_delta 的值应优先于 message_start", () => {
- const sseResponse = [
- "event: message_start",
- 'data: {"type":"message_start","message":{"usage":{"input_tokens":100,"output_tokens":50}}}',
- "",
- "event: message_delta",
- 'data: {"type":"message_delta","usage":{"input_tokens":1000,"output_tokens":500}}',
- "",
- ].join("\n");
- const result = parseUsageFromResponseText(sseResponse, "claude");
- // message_delta 优先
- expect(result.usageMetrics?.input_tokens).toBe(1000);
- expect(result.usageMetrics?.output_tokens).toBe(500);
- });
- it("message_start 的 cache 细分应补充 message_delta 缺失的字段", () => {
- const sseResponse = [
- "event: message_start",
- 'data: {"type":"message_start","message":{"usage":{"cache_creation":{"ephemeral_5m_input_tokens":200,"ephemeral_1h_input_tokens":300}}}}',
- "",
- "event: message_delta",
- 'data: {"type":"message_delta","usage":{"input_tokens":1000,"output_tokens":500,"cache_creation_input_tokens":500}}',
- "",
- ].join("\n");
- const result = parseUsageFromResponseText(sseResponse, "claude");
- // message_delta 的值
- expect(result.usageMetrics?.input_tokens).toBe(1000);
- expect(result.usageMetrics?.output_tokens).toBe(500);
- expect(result.usageMetrics?.cache_creation_input_tokens).toBe(500);
- // message_start 补充的细分字段
- expect(result.usageMetrics?.cache_creation_5m_input_tokens).toBe(200);
- expect(result.usageMetrics?.cache_creation_1h_input_tokens).toBe(300);
- });
- });
- describe("Codex provider 特殊处理", () => {
- it("Codex 应从 input_tokens 中减去 cached_tokens", () => {
- const response = JSON.stringify({
- usage: {
- input_tokens: 1000,
- output_tokens: 500,
- cache_read_input_tokens: 300,
- },
- });
- const result = parseUsageFromResponseText(response, "codex");
- // adjustUsageForProviderType 会调整 input_tokens
- expect(result.usageMetrics?.input_tokens).toBe(700); // 1000 - 300
- expect(result.usageMetrics?.cache_read_input_tokens).toBe(300);
- });
- });
- describe("边界情况", () => {
- it("应处理所有值为 0 的情况", () => {
- const response = JSON.stringify({
- usage: {
- input_tokens: 0,
- output_tokens: 0,
- cache_creation_input_tokens: 0,
- cache_read_input_tokens: 0,
- },
- });
- const result = parseUsageFromResponseText(response, "claude");
- expect(result.usageMetrics).not.toBeNull();
- expect(result.usageMetrics?.input_tokens).toBe(0);
- expect(result.usageMetrics?.output_tokens).toBe(0);
- });
- it("应处理部分字段缺失的情况", () => {
- const response = JSON.stringify({
- usage: {
- input_tokens: 1000,
- },
- });
- const result = parseUsageFromResponseText(response, "claude");
- expect(result.usageMetrics?.input_tokens).toBe(1000);
- expect(result.usageMetrics?.output_tokens).toBeUndefined();
- expect(result.usageMetrics?.cache_creation_input_tokens).toBeUndefined();
- });
- it("应处理无效的 JSON", () => {
- const result = parseUsageFromResponseText("invalid json", "claude");
- expect(result.usageMetrics).toBeNull();
- });
- it("应处理空的 usage 对象", () => {
- const response = JSON.stringify({
- usage: {},
- });
- const result = parseUsageFromResponseText(response, "claude");
- expect(result.usageMetrics).toBeNull();
- });
- });
- });
|