provider-text-parser.test.ts 8.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242
  1. import { describe, expect, test } from "vitest";
  2. import {
  3. detectProviderType,
  4. extractApiKey,
  5. extractProviderName,
  6. extractUrl,
  7. generateRandomSuffix,
  8. isValidApiKeyFormat,
  9. parseProviderText,
  10. } from "@/lib/utils/provider-text-parser";
  11. describe("provider-text-parser", () => {
  12. describe("detectProviderType", () => {
  13. test("should detect claude type keywords", () => {
  14. expect(detectProviderType("https://api.anthropic.com/v1/messages")).toBe("claude");
  15. expect(detectProviderType("Claude API Key")).toBe("claude");
  16. expect(detectProviderType("anthropic")).toBe("claude");
  17. });
  18. test("should detect codex type keywords", () => {
  19. expect(detectProviderType("https://api.openai.com/v1/responses")).toBe("codex");
  20. expect(detectProviderType("GPT-4 API")).toBe("codex");
  21. expect(detectProviderType("codex endpoint")).toBe("codex");
  22. });
  23. test("should detect gemini type keywords", () => {
  24. expect(detectProviderType("https://generativelanguage.googleapis.com/v1beta")).toBe("gemini");
  25. expect(detectProviderType("Vertex AI")).toBe("gemini");
  26. expect(detectProviderType("google gemini")).toBe("gemini");
  27. });
  28. test("should detect openai-compatible type keywords", () => {
  29. expect(detectProviderType("/v1/chat/completions")).toBe("openai-compatible");
  30. expect(detectProviderType("OpenAI Compatible")).toBe("openai-compatible");
  31. });
  32. test("should return default type claude for unmatched text", () => {
  33. expect(detectProviderType("random text")).toBe("claude");
  34. expect(detectProviderType("")).toBe("claude");
  35. });
  36. });
  37. describe("extractUrl", () => {
  38. test("should extract valid URL", () => {
  39. expect(extractUrl("URL: https://api.example.com/v1/messages")).toBe(
  40. "https://api.example.com/v1/messages"
  41. );
  42. });
  43. test("should extract HTTP URL", () => {
  44. expect(extractUrl("endpoint: http://localhost:8080/v1/messages")).toBe(
  45. "http://localhost:8080/v1/messages"
  46. );
  47. });
  48. test("should remove trailing punctuation", () => {
  49. expect(extractUrl("Check https://api.test.com/v1.")).toBe("https://api.test.com/v1");
  50. expect(extractUrl("URL: https://api.test.com/v1,")).toBe("https://api.test.com/v1");
  51. });
  52. test("should handle multiple trailing punctuation", () => {
  53. expect(extractUrl("URL: https://api.test.com/v1...")).toBe("https://api.test.com/v1");
  54. expect(extractUrl("(https://api.test.com/v1)")).toBe("https://api.test.com/v1");
  55. });
  56. test("should return null for text without URL", () => {
  57. expect(extractUrl("no url here")).toBeNull();
  58. });
  59. test("should prioritize API URL", () => {
  60. const text = "Website: https://example.com, API: https://api.example.com/v1/messages";
  61. expect(extractUrl(text)).toBe("https://api.example.com/v1/messages");
  62. });
  63. test("should fallback to first valid URL when no API indicators", () => {
  64. expect(extractUrl("site: https://example.com and https://test.com")).toBe(
  65. "https://example.com"
  66. );
  67. });
  68. test("should handle URL with query parameters", () => {
  69. const url = "https://api.example.com/v1/messages?key=value";
  70. expect(extractUrl(`URL: ${url}`)).toBe(url);
  71. });
  72. });
  73. describe("extractApiKey", () => {
  74. test("should extract OpenAI format key", () => {
  75. const key = "sk-1234567890abcdefghij1234567890ab";
  76. expect(extractApiKey(`key: ${key}`)).toBe(key);
  77. });
  78. test("should extract Anthropic format key", () => {
  79. const key = "sk-ant-abcdef1234567890ghijklmnopqrstuvwxyz";
  80. expect(extractApiKey(`key: ${key}`)).toBe(key);
  81. });
  82. test("should extract Google format key", () => {
  83. const key = "AIzaSyAbcdefghijklmnopqrstuvwxyz123456";
  84. expect(extractApiKey(`key: ${key}`)).toBe(key);
  85. });
  86. test("should return null for text without key", () => {
  87. expect(extractApiKey("no key")).toBeNull();
  88. expect(extractApiKey("short")).toBeNull();
  89. });
  90. test("should extract generic long key", () => {
  91. const key = "abcdefghijklmnopqrstuvwxyz123456";
  92. expect(extractApiKey(`api_key: ${key}`)).toBe(key);
  93. });
  94. });
  95. describe("extractProviderName", () => {
  96. test("should extract name from name: format", () => {
  97. expect(extractProviderName("name: MyProvider")).toBe("MyProvider");
  98. expect(extractProviderName("Name: TestAPI")).toBe("TestAPI");
  99. });
  100. test("should extract name from provider: format", () => {
  101. expect(extractProviderName("provider: TestAPI")).toBe("TestAPI");
  102. });
  103. test("should extract name from service: format", () => {
  104. expect(extractProviderName("service: MyService")).toBe("MyService");
  105. });
  106. test("should return null for text without name", () => {
  107. expect(extractProviderName("https://api.test.com")).toBeNull();
  108. });
  109. test("should not extract URL as name", () => {
  110. expect(extractProviderName("https://api.test.com/v1")).toBeNull();
  111. });
  112. test("should not extract API key as name", () => {
  113. expect(extractProviderName("sk-1234567890abcdefghij")).toBeNull();
  114. });
  115. test("should reject names with 2 or fewer characters", () => {
  116. expect(extractProviderName("name: AB")).toBe("name");
  117. });
  118. test("should accept names with exactly 3 characters", () => {
  119. expect(extractProviderName("provider: ABC")).toBe("ABC");
  120. });
  121. test("should accept names up to 60 characters", () => {
  122. const name60 = "A".repeat(60);
  123. expect(extractProviderName(`provider: ${name60}`)).toBe(name60);
  124. });
  125. test("should fallback to label when value exceeds 60 characters", () => {
  126. const name61 = "A".repeat(61);
  127. expect(extractProviderName(`name: ${name61}`)).toBe("name");
  128. });
  129. });
  130. describe("generateRandomSuffix", () => {
  131. test("should generate 4 character string", () => {
  132. const suffix = generateRandomSuffix();
  133. expect(suffix).toHaveLength(4);
  134. expect(/^[a-z0-9]+$/.test(suffix)).toBe(true);
  135. });
  136. test("should generate different values", () => {
  137. const suffixes = new Set(Array.from({ length: 100 }, generateRandomSuffix));
  138. expect(suffixes.size).toBeGreaterThan(90);
  139. });
  140. });
  141. describe("isValidApiKeyFormat", () => {
  142. test("should return true for valid key format", () => {
  143. expect(isValidApiKeyFormat("sk-1234567890abcdefghij")).toBe(true);
  144. expect(isValidApiKeyFormat("abcdefghijklmnopqrstuvwxyz123456")).toBe(true);
  145. });
  146. test("should return false for short key", () => {
  147. expect(isValidApiKeyFormat("short")).toBe(false);
  148. expect(isValidApiKeyFormat("")).toBe(false);
  149. });
  150. test("should return false for key with special characters", () => {
  151. expect(isValidApiKeyFormat("sk-test@key#invalid!")).toBe(false);
  152. });
  153. test("should return false for key with exactly 19 characters", () => {
  154. expect(isValidApiKeyFormat("a".repeat(19))).toBe(false);
  155. });
  156. test("should return true for key with exactly 20 characters", () => {
  157. expect(isValidApiKeyFormat("a".repeat(20))).toBe(true);
  158. });
  159. test("should accept keys with underscores and hyphens", () => {
  160. expect(isValidApiKeyFormat("sk_test-key_12345678901234")).toBe(true);
  161. });
  162. });
  163. describe("parseProviderText", () => {
  164. test("should parse complete configuration text", () => {
  165. const text = `
  166. name: Privnode
  167. URL: https://api.privnode.com/v1/messages
  168. API Key: sk-ant-1234567890abcdefghijklmnopqrstuvwxyz
  169. `;
  170. const result = parseProviderText(text);
  171. expect(result.name).toBe("Privnode");
  172. expect(result.url).toBe("https://api.privnode.com/v1/messages");
  173. expect(result.key).toMatch(/^sk-ant-/);
  174. expect(result.providerType).toBe("claude");
  175. expect(result.confidence.name).toBe(true);
  176. expect(result.confidence.url).toBe(true);
  177. expect(result.confidence.key).toBe(true);
  178. });
  179. test("should handle minimal input", () => {
  180. const result = parseProviderText("https://api.openai.com/v1/responses");
  181. expect(result.url).toBeTruthy();
  182. expect(result.providerType).toBe("codex");
  183. });
  184. test("should handle empty input", () => {
  185. const result = parseProviderText("");
  186. expect(result.name).toBeNull();
  187. expect(result.url).toBeNull();
  188. expect(result.key).toBeNull();
  189. expect(result.providerType).toBe("claude");
  190. });
  191. test("should set confidence.type to true for non-claude providers", () => {
  192. const result = parseProviderText("https://api.openai.com/v1/responses");
  193. expect(result.confidence.type).toBe(true);
  194. });
  195. test("should set confidence.type to false for claude provider", () => {
  196. const result = parseProviderText("anthropic API");
  197. expect(result.confidence.type).toBe(false);
  198. });
  199. });
  200. });