aegida-console / lib / chat / validation.test.ts
validation.test.ts
Raw
import { describe, expect, it } from "vitest";

import { isModelId } from "@/lib/chat/types";
import {
  ChatValidationError,
  validateChatRequest,
  validatePersistentChatRequest,
} from "@/lib/chat/validation";

describe("isModelId", () => {
  it.each(["auto", "qwen", "claude-sonnet.4", `m${"x".repeat(127)}`])(
    "accepts the Gate model slug %j",
    (value) => expect(isModelId(value)).toBe(true),
  );

  it.each(["", "Auto", "model/path", ".leading", "-leading", `m${"x".repeat(128)}`])(
    "rejects the invalid Gate model slug %j",
    (value) => expect(isModelId(value)).toBe(false),
  );
});

describe("validateChatRequest", () => {
  it("returns a normalized request containing only supported fields", () => {
    expect(
      validateChatRequest({
        model: "chatgpt",
        ignored: "value",
        messages: [
          { role: "user", content: "Привет", ignored: "value" },
          { role: "assistant", content: "Здравствуйте" },
        ],
      }),
    ).toEqual({
      model: "chatgpt",
      messages: [
        { role: "user", content: "Привет" },
        { role: "assistant", content: "Здравствуйте" },
      ],
    });
  });

  it.each([null, [], "request", 1])("rejects a non-object request: %j", (value) => {
    expect(() => validateChatRequest(value)).toThrow(ChatValidationError);
  });

  it("rejects an unknown model", () => {
    expect(() => validateChatRequest({ model: "other", messages: [] })).toThrow(
      ChatValidationError,
    );
  });

  it("accepts a dynamic Gate model in both chat request shapes", () => {
    expect(
      validateChatRequest({
        model: "claude-sonnet.4",
        messages: [{ role: "user", content: "Привет" }],
      }).model,
    ).toBe("claude-sonnet.4");
    expect(
      validatePersistentChatRequest({
        model: "claude-sonnet.4",
        content: "Привет",
      }).model,
    ).toBe("claude-sonnet.4");
  });

  it("rejects an empty message array", () => {
    expect(() => validateChatRequest({ model: "qwen", messages: [] })).toThrow(
      ChatValidationError,
    );
  });

  it("rejects more than 100 messages", () => {
    expect(() =>
      validateChatRequest({
        model: "qwen",
        messages: Array.from({ length: 101 }, () => ({ role: "user", content: "x" })),
      }),
    ).toThrow(ChatValidationError);
  });

  it("accepts the exact message-count and character boundaries", () => {
    expect(
      validateChatRequest({
        model: "qwen",
        messages: Array.from({ length: 100 }, () => ({
          role: "user",
          content: "x",
        })),
      }).messages,
    ).toHaveLength(100);

    expect(
      validateChatRequest({
        model: "qwen",
        messages: Array.from({ length: 4 }, () => ({
          role: "user",
          content: "x".repeat(32_000),
        })),
      }).messages,
    ).toHaveLength(4);
  });

  it("rejects missing entries in a sparse message array", () => {
    expect(() =>
      validateChatRequest({ model: "qwen", messages: new Array(1) }),
    ).toThrow(ChatValidationError);
  });

  it.each(["system", "tool", null])("rejects an unsupported message role: %j", (role) => {
    expect(() =>
      validateChatRequest({ model: "deepseek", messages: [{ role, content: "x" }] }),
    ).toThrow(ChatValidationError);
  });

  it.each(["", " \n\t", null])("rejects empty or non-string content: %j", (content) => {
    expect(() =>
      validateChatRequest({ model: "deepseek", messages: [{ role: "user", content }] }),
    ).toThrow(ChatValidationError);
  });

  it("rejects a message longer than 32,000 characters", () => {
    expect(() =>
      validateChatRequest({
        model: "chatgpt",
        messages: [{ role: "user", content: "x".repeat(32_001) }],
      }),
    ).toThrow(ChatValidationError);
  });

  it("rejects more than 128,000 total characters", () => {
    expect(() =>
      validateChatRequest({
        model: "chatgpt",
        messages: Array.from({ length: 5 }, () => ({
          role: "user",
          content: "x".repeat(25_600),
        })).concat({ role: "user", content: "x" }),
      }),
    ).toThrow(ChatValidationError);
  });
});