systemprompt-service.test.ts•15.1 kB
import {
  jest,
  describe,
  it,
  expect,
  beforeEach,
  afterEach,
} from "@jest/globals";
import type { SpyInstance } from "jest-mock";
import { SystemPromptService } from "../systemprompt-service";
import type {
  SystempromptPromptResponse,
  SystempromptBlockResponse,
  SystempromptAgentResponse,
  SystempromptUserStatusResponse,
  SystempromptPromptRequest,
  SystempromptBlockRequest,
  SystempromptAgentRequest,
  Metadata,
} from "../../types/index.js";
describe("SystemPromptService", () => {
  const mockApiKey = "test-api-key";
  const mockBaseUrl = "http://test-api.com";
  let fetchSpy: SpyInstance<typeof fetch>;
  beforeEach(() => {
    // Reset the singleton instance
    SystemPromptService.cleanup();
    // Reset fetch mock
    fetchSpy = jest
      .spyOn(global, "fetch")
      .mockImplementation(
        async (input: string | URL | Request, init?: RequestInit) => {
          const url =
            input instanceof URL ? input.toString() : input.toString();
          // Handle error cases
          if (url.includes("invalid-api-key")) {
            return new Response(
              JSON.stringify({ message: "Invalid API key" }),
              {
                status: 403,
                headers: { "Content-Type": "application/json" },
              }
            );
          }
          if (url.includes("not-found")) {
            return new Response(
              JSON.stringify({
                message: "Resource not found - it may have been deleted",
              }),
              { status: 404, headers: { "Content-Type": "application/json" } }
            );
          }
          if (url.includes("conflict")) {
            return new Response(
              JSON.stringify({
                message: "Resource conflict - it may have been edited",
              }),
              { status: 409, headers: { "Content-Type": "application/json" } }
            );
          }
          if (url.includes("bad-request")) {
            return new Response(JSON.stringify({ message: "Invalid data" }), {
              status: 400,
              headers: { "Content-Type": "application/json" },
            });
          }
          if (url.includes("invalid-json")) {
            return new Response("invalid json", {
              status: 200,
              headers: { "Content-Type": "application/json" },
            });
          }
          // Handle successful cases
          if (init?.method === "DELETE") {
            return new Response(null, { status: 204 });
          }
          return new Response(JSON.stringify({ data: "test" }), {
            status: 200,
            statusText: "OK",
            headers: new Headers({
              "Content-Type": "application/json",
            }),
          });
        }
      );
  });
  afterEach(() => {
    fetchSpy.mockRestore();
  });
  describe("initialization", () => {
    it("should initialize with API key", () => {
      SystemPromptService.initialize(mockApiKey);
      const instance = SystemPromptService.getInstance();
      expect(instance).toBeDefined();
    });
    it("should initialize with custom base URL", () => {
      SystemPromptService.initialize(mockApiKey, mockBaseUrl);
      const instance = SystemPromptService.getInstance();
      expect(instance).toBeDefined();
    });
    it("should throw error if initialized without API key", () => {
      expect(() => SystemPromptService.initialize("")).toThrow(
        "API key is required"
      );
    });
    it("should throw error if getInstance called before initialization", () => {
      expect(() => SystemPromptService.getInstance()).toThrow(
        "SystemPromptService must be initialized with an API key first"
      );
    });
  });
  describe("API requests", () => {
    let service: SystemPromptService;
    beforeEach(() => {
      SystemPromptService.initialize(mockApiKey, mockBaseUrl);
      service = SystemPromptService.getInstance();
    });
    it("should handle successful GET request", async () => {
      const mockResponse = { data: "test" };
      const result = await service.getAllPrompts();
      expect(result).toEqual(mockResponse);
      expect(fetchSpy).toHaveBeenCalledWith(
        `${mockBaseUrl}/prompt`,
        expect.objectContaining({
          method: "GET",
          headers: {
            "Content-Type": "application/json",
            "api-key": mockApiKey,
          },
        })
      );
    });
    it("should handle successful POST request", async () => {
      const data = {
        metadata: {
          title: "Test",
          description: "Test description",
          version: 1,
          status: "active",
          author: "test",
          log_message: "test",
        },
        instruction: {
          static: "Test instruction",
        },
        input: {
          type: ["text"],
        },
        output: {
          type: ["text"],
        },
      };
      const mockResponse = { data: "test" };
      const result = await service.createPrompt(data);
      expect(result).toEqual(mockResponse);
      expect(fetchSpy).toHaveBeenCalledWith(
        `${mockBaseUrl}/prompt`,
        expect.objectContaining({
          method: "POST",
          headers: {
            "Content-Type": "application/json",
            "api-key": mockApiKey,
          },
          body: JSON.stringify(data),
        })
      );
    });
    it("should handle 204 response", async () => {
      await service.deletePrompt("test-id");
      expect(fetchSpy).toHaveBeenCalledWith(
        `${mockBaseUrl}/prompt/test-id`,
        expect.objectContaining({
          method: "DELETE",
          headers: {
            "Content-Type": "application/json",
            "api-key": mockApiKey,
          },
        })
      );
    });
    it("should handle invalid API key error", async () => {
      fetchSpy.mockImplementationOnce(() =>
        Promise.resolve(
          new Response(JSON.stringify({ message: "Invalid API key" }), {
            status: 403,
            headers: { "Content-Type": "application/json" },
          })
        )
      );
      await expect(service.getAllPrompts()).rejects.toThrow("Invalid API key");
    });
    it("should handle not found error", async () => {
      await expect(service.getBlock("not-found")).rejects.toThrow(
        "Resource not found - it may have been deleted"
      );
    });
    it("should handle conflict error", async () => {
      await expect(service.editPrompt("conflict", {})).rejects.toThrow(
        "Resource conflict - it may have been edited"
      );
    });
    it("should handle bad request error", async () => {
      fetchSpy.mockImplementationOnce(() =>
        Promise.resolve(
          new Response(JSON.stringify({ message: "Invalid data" }), {
            status: 400,
            headers: { "Content-Type": "application/json" },
          })
        )
      );
      const invalidData: SystempromptPromptRequest = {
        metadata: {
          title: "Test",
          description: "Test description",
          version: 1,
          status: "active",
          author: "test",
          log_message: "test",
          tag: ["test"],
        },
        instruction: { static: "Test instruction" },
      };
      await expect(service.createPrompt(invalidData)).rejects.toThrow(
        "Invalid data"
      );
    });
    it("should handle network error", async () => {
      fetchSpy.mockImplementationOnce(() =>
        Promise.reject(new Error("Failed to fetch"))
      );
      await expect(service.getAllPrompts()).rejects.toThrow(
        "API request failed"
      );
    });
    it("should handle JSON parse error", async () => {
      fetchSpy.mockImplementationOnce(() =>
        Promise.resolve(
          new Response("invalid json", {
            status: 200,
            headers: { "Content-Type": "application/json" },
          })
        )
      );
      await expect(service.getAllPrompts()).rejects.toThrow(
        "Failed to parse API response"
      );
    });
  });
  describe("API endpoints", () => {
    let service: SystemPromptService;
    beforeEach(() => {
      SystemPromptService.initialize(mockApiKey, mockBaseUrl);
      service = SystemPromptService.getInstance();
      fetchSpy.mockResolvedValue(
        new Response(JSON.stringify({}), {
          status: 200,
          headers: { "Content-Type": "application/json" },
        })
      );
    });
    it("should call getAllPrompts endpoint", async () => {
      await service.getAllPrompts();
      expect(fetchSpy).toHaveBeenCalledWith(
        expect.stringContaining("/prompt"),
        expect.objectContaining({
          method: "GET",
          headers: {
            "Content-Type": "application/json",
            "api-key": "test-api-key",
          },
        })
      );
    });
    it("should call createPrompt endpoint", async () => {
      const data = {
        metadata: {
          title: "Test",
          description: "Test description",
          version: 1,
          status: "active",
          author: "test",
          log_message: "test",
        },
        instruction: {
          static: "Test instruction",
        },
        input: {
          type: ["text"],
        },
        output: {
          type: ["text"],
        },
      };
      await service.createPrompt(data);
      expect(fetchSpy).toHaveBeenCalledWith(
        expect.stringContaining("/prompt"),
        expect.objectContaining({
          method: "POST",
          headers: {
            "Content-Type": "application/json",
            "api-key": "test-api-key",
          },
          body: JSON.stringify(data),
        })
      );
    });
    it("should call editPrompt endpoint", async () => {
      const data = {
        metadata: {
          title: "Test",
          description: "Test description",
          version: 1,
          status: "active",
          author: "test",
          log_message: "test",
        },
      };
      await service.editPrompt("test-id", data);
      expect(fetchSpy).toHaveBeenCalledWith(
        expect.stringContaining("/prompt/test-id"),
        expect.objectContaining({
          method: "PUT",
          headers: {
            "Content-Type": "application/json",
            "api-key": "test-api-key",
          },
          body: JSON.stringify(data),
        })
      );
    });
    it("should call deletePrompt endpoint", async () => {
      await service.deletePrompt("test-id");
      expect(fetchSpy).toHaveBeenCalledWith(
        expect.stringContaining("/prompt/test-id"),
        expect.objectContaining({
          method: "DELETE",
          headers: {
            "Content-Type": "application/json",
            "api-key": "test-api-key",
          },
        })
      );
    });
    it("should call createBlock endpoint", async () => {
      const data = {
        content: "test",
        prefix: "test",
        metadata: {
          title: "Test",
          description: "Test description",
        },
      };
      await service.createBlock(data);
      expect(fetchSpy).toHaveBeenCalledWith(
        expect.stringContaining("/block"),
        expect.objectContaining({
          method: "POST",
          headers: {
            "Content-Type": "application/json",
            "api-key": "test-api-key",
          },
          body: JSON.stringify(data),
        })
      );
    });
    it("should call editBlock endpoint", async () => {
      const data = {
        content: "test",
        metadata: {
          title: "Test",
        },
      };
      await service.editBlock("test-id", data);
      expect(fetchSpy).toHaveBeenCalledWith(
        expect.stringContaining("/block/test-id"),
        expect.objectContaining({
          method: "PUT",
          headers: {
            "Content-Type": "application/json",
            "api-key": "test-api-key",
          },
          body: JSON.stringify(data),
        })
      );
    });
    it("should call listBlocks endpoint", async () => {
      await service.listBlocks();
      expect(fetchSpy).toHaveBeenCalledWith(
        expect.stringContaining("/block"),
        expect.objectContaining({
          method: "GET",
          headers: {
            "Content-Type": "application/json",
            "api-key": "test-api-key",
          },
        })
      );
    });
    it("should call getBlock endpoint", async () => {
      await service.getBlock("test-id");
      expect(fetchSpy).toHaveBeenCalledWith(
        expect.stringContaining("/block/test-id"),
        expect.objectContaining({
          method: "GET",
          headers: {
            "Content-Type": "application/json",
            "api-key": "test-api-key",
          },
        })
      );
    });
    it("should call listAgents endpoint", async () => {
      await service.listAgents();
      expect(fetchSpy).toHaveBeenCalledWith(
        expect.stringContaining("/agent"),
        expect.objectContaining({
          method: "GET",
          headers: {
            "Content-Type": "application/json",
            "api-key": "test-api-key",
          },
        })
      );
    });
    it("should call createAgent endpoint", async () => {
      const data = {
        content: "test",
        metadata: {
          title: "Test",
          description: "Test description",
        },
      };
      await service.createAgent(data);
      expect(fetchSpy).toHaveBeenCalledWith(
        expect.stringContaining("/agent"),
        expect.objectContaining({
          method: "POST",
          headers: {
            "Content-Type": "application/json",
            "api-key": "test-api-key",
          },
          body: JSON.stringify(data),
        })
      );
    });
    it("should call editAgent endpoint", async () => {
      const data = {
        content: "test",
        metadata: {
          title: "Test",
        },
      };
      await service.editAgent("test-id", data);
      expect(fetchSpy).toHaveBeenCalledWith(
        expect.stringContaining("/agent/test-id"),
        expect.objectContaining({
          method: "PUT",
          headers: {
            "Content-Type": "application/json",
            "api-key": "test-api-key",
          },
          body: JSON.stringify(data),
        })
      );
    });
    it("should call deleteBlock endpoint", async () => {
      await service.deleteBlock("test-id");
      expect(fetchSpy).toHaveBeenCalledWith(
        expect.stringContaining("/block/test-id"),
        expect.objectContaining({
          method: "DELETE",
          headers: {
            "Content-Type": "application/json",
            "api-key": "test-api-key",
          },
        })
      );
    });
    it("should call fetchUserStatus endpoint", async () => {
      await service.fetchUserStatus();
      expect(fetchSpy).toHaveBeenCalledWith(
        expect.stringContaining("/user/mcp"),
        expect.objectContaining({
          method: "GET",
          headers: {
            "Content-Type": "application/json",
            "api-key": "test-api-key",
          },
        })
      );
    });
  });
});