diff --git a/src/client/snippets.ts b/src/client/snippets.ts index bd64045..e17cc2a 100644 --- a/src/client/snippets.ts +++ b/src/client/snippets.ts @@ -1,5 +1,5 @@ import { - CreateSnippetRequest, + CreateSnippetParams, CreateSnippetResponse, CreateSnippetResponseSchema, DeleteSnippetParams, @@ -11,7 +11,6 @@ import { GetSnippetsResponse, GetSnippetsResponseSchema, UpdateSnippetParams, - UpdateSnippetRequest, UpdateSnippetResponse, UpdateSnippetResponseSchema, } from "../types/snippets.js"; @@ -33,7 +32,7 @@ export function Snippets>(Base: T) { } async createSnippet( - params: CreateSnippetRequest, + params: CreateSnippetParams, opts?: { signal?: AbortSignal } ): Promise { const response = await this.client.post("/api/snippets", params, opts); @@ -53,11 +52,11 @@ export function Snippets>(Base: T) { async updateSnippet( params: UpdateSnippetParams, - body: UpdateSnippetRequest, opts?: { signal?: AbortSignal } ): Promise { + const { identifier, ...body } = params; const response = await this.client.put( - `/api/snippets/${params.identifier}`, + `/api/snippets/${identifier}`, body, opts ); diff --git a/src/client/templates.ts b/src/client/templates.ts index 8a0aebb..8b9b621 100644 --- a/src/client/templates.ts +++ b/src/client/templates.ts @@ -19,9 +19,9 @@ import { PreviewTemplateParams, PushTemplate, PushTemplateSchema, + SendTemplateProofParams, SMSTemplate, SMSTemplateSchema, - TemplateProofRequest, UpdateEmailTemplateParams, UpdateInAppTemplateParams, UpdatePushTemplateParams, @@ -91,7 +91,7 @@ export function Templates>(Base: T) { async #sendTemplateProof( pathSegment: string, - request: TemplateProofRequest + request: SendTemplateProofParams ): Promise { const response = await this.client.post( `/api/templates/${pathSegment}/proof`, @@ -181,7 +181,7 @@ export function Templates>(Base: T) { } async sendEmailTemplateProof( - request: TemplateProofRequest + request: SendTemplateProofParams ): Promise { return this.#sendTemplateProof("email", request); } @@ -208,7 +208,7 @@ export function Templates>(Base: T) { } async sendSMSTemplateProof( - request: TemplateProofRequest + request: SendTemplateProofParams ): Promise { return this.#sendTemplateProof("sms", request); } @@ -231,7 +231,7 @@ export function Templates>(Base: T) { } async sendPushTemplateProof( - request: TemplateProofRequest + request: SendTemplateProofParams ): Promise { return this.#sendTemplateProof("push", request); } @@ -254,7 +254,7 @@ export function Templates>(Base: T) { } async sendInAppTemplateProof( - request: TemplateProofRequest + request: SendTemplateProofParams ): Promise { return this.#sendTemplateProof("inapp", request); } diff --git a/src/types/snippets.ts b/src/types/snippets.ts index 4846cf1..1e8bd0b 100644 --- a/src/types/snippets.ts +++ b/src/types/snippets.ts @@ -30,8 +30,8 @@ export const SnippetResponseSchema = z.object({ export type SnippetResponse = z.infer; -// Request schemas -export const CreateSnippetRequestSchema = z.object({ +// Parameter schemas for creating snippets +export const CreateSnippetParamsSchema = z.object({ content: z .string() .describe( @@ -57,30 +57,7 @@ export const CreateSnippetRequestSchema = z.object({ ), }); -export type CreateSnippetRequest = z.infer; - -export const UpdateSnippetRequestSchema = z.object({ - content: z - .string() - .describe( - 'Content of the snippet. Handlebars must be valid. Disallowed content: script tags with JS sources or non-JSON content, inline JS event handlers (e.g., onload="..."), and javascript: in href or src attributes (anchors and iframes).' - ), - description: z.string().optional().describe("Description of the snippet"), - createdByUserId: z - .string() - .optional() - .describe( - "User ID (email) of the updater. If not provided, defaults to the project creator." - ), - variables: z - .array(z.string()) - .optional() - .describe( - "List of variable names used in the content with a Handlebars expression such as {{myField}}. Variable names are case-sensitive and should be simple identifiers (letters, numbers, underscores). To learn more about using Handlebars in Snippets, see Customizing Snippets with Variables." - ), -}); - -export type UpdateSnippetRequest = z.infer; +export type CreateSnippetParams = z.infer; // Response schemas export const GetSnippetsResponseSchema = z.object({ @@ -113,7 +90,7 @@ export const DeleteSnippetResponseSchema = z.object({ export type DeleteSnippetResponse = z.infer; -// Parameter schemas +// Identifier schema for get/update/delete operations export const SnippetIdentifierSchema = z .union([ z.string().describe("Snippet name"), @@ -133,6 +110,24 @@ export type GetSnippetParams = z.infer; export const UpdateSnippetParamsSchema = z.object({ identifier: SnippetIdentifierSchema, + content: z + .string() + .describe( + 'Content of the snippet. Handlebars must be valid. Disallowed content: script tags with JS sources or non-JSON content, inline JS event handlers (e.g., onload="..."), and javascript: in href or src attributes (anchors and iframes).' + ), + description: z.string().optional().describe("Description of the snippet"), + createdByUserId: z + .string() + .optional() + .describe( + "User ID (email) of the updater. If not provided, defaults to the project creator." + ), + variables: z + .array(z.string()) + .optional() + .describe( + "List of variable names used in the content with a Handlebars expression such as {{myField}}. Variable names are case-sensitive and should be simple identifiers (letters, numbers, underscores). To learn more about using Handlebars in Snippets, see Customizing Snippets with Variables." + ), }); export type UpdateSnippetParams = z.infer; diff --git a/src/types/templates.ts b/src/types/templates.ts index 0c87911..0f14183 100644 --- a/src/types/templates.ts +++ b/src/types/templates.ts @@ -459,7 +459,7 @@ export type BulkDeleteTemplatesResponse = z.infer< >; // Template proof schemas -export const TemplateProofRequestSchema = z +export const SendTemplateProofParamsSchema = z .object({ templateId: z.number().describe("Template ID to send proof for"), recipientEmail: z @@ -490,7 +490,9 @@ export const TemplateProofRequestSchema = z path: ["recipientEmail", "recipientUserId"], }); -export type TemplateProofRequest = z.infer; +export type SendTemplateProofParams = z.infer< + typeof SendTemplateProofParamsSchema +>; // Template preview schemas export const TemplatePreviewRequestSchema = z.object({ diff --git a/tests/integration/snippets.test.ts b/tests/integration/snippets.test.ts index af1ac22..b3a02e4 100644 --- a/tests/integration/snippets.test.ts +++ b/tests/integration/snippets.test.ts @@ -141,14 +141,12 @@ describe("Snippets Management Integration Tests", () => { const updateResponse = await retryRateLimited( () => withTimeout( - client.updateSnippet( - { identifier: testSnippetName }, - { - content: updatedContent, - description: "Updated CRUD test snippet", - variables: ["firstName", "company"], - } - ) + client.updateSnippet({ + identifier: testSnippetName, + content: updatedContent, + description: "Updated CRUD test snippet", + variables: ["firstName", "company"], + }) ), "Update snippet by name" ); @@ -160,13 +158,11 @@ describe("Snippets Management Integration Tests", () => { const updateByIdResponse = await retryRateLimited( () => withTimeout( - client.updateSnippet( - { identifier: snippetId }, - { - content: updateByIdContent, - variables: ["user"], - } - ) + client.updateSnippet({ + identifier: snippetId, + content: updateByIdContent, + variables: ["user"], + }) ), "Update snippet by ID" ); diff --git a/tests/unit/snippets.test.ts b/tests/unit/snippets.test.ts index 289c9f0..cdf536e 100644 --- a/tests/unit/snippets.test.ts +++ b/tests/unit/snippets.test.ts @@ -9,9 +9,9 @@ import { import { IterableClient } from "../../src/client"; import { - CreateSnippetRequestSchema, + CreateSnippetParamsSchema, SnippetResponseSchema, - UpdateSnippetRequestSchema, + UpdateSnippetParamsSchema, } from "../../src/types/snippets.js"; import { createMockClient, createMockSnippet } from "../utils/test-helpers"; @@ -158,15 +158,19 @@ describe("Snippets Management", () => { mockAxiosInstance.put.mockResolvedValue(mockResponse); const params = { + identifier: "test-snippet", content: "

Updated content {{name}}!

", description: "Updated description", }; - await client.updateSnippet({ identifier: "test-snippet" }, params); + await client.updateSnippet(params); expect(mockAxiosInstance.put).toHaveBeenCalledWith( "/api/snippets/test-snippet", - params, + { + content: "

Updated content {{name}}!

", + description: "Updated description", + }, undefined ); }); @@ -176,10 +180,11 @@ describe("Snippets Management", () => { mockAxiosInstance.put.mockResolvedValue(mockResponse); const params = { + identifier: 12345, content: "

Updated content!

", }; - const result = await client.updateSnippet({ identifier: 12345 }, params); + const result = await client.updateSnippet(params); expect(result).toHaveProperty("snippetId", 12345); }); @@ -241,7 +246,7 @@ describe("Snippets Management", () => { ).toThrow(); }); - it("should validate create snippet request schema", () => { + it("should validate create snippet params schema", () => { const validRequest = { name: "test-snippet", content: "

Hello {{name}}!

", @@ -251,16 +256,16 @@ describe("Snippets Management", () => { // Valid request expect(() => - CreateSnippetRequestSchema.parse(validRequest) + CreateSnippetParamsSchema.parse(validRequest) ).not.toThrow(); // Missing required fields expect(() => - CreateSnippetRequestSchema.parse({ ...validRequest, name: undefined }) + CreateSnippetParamsSchema.parse({ ...validRequest, name: undefined }) ).toThrow(); expect(() => - CreateSnippetRequestSchema.parse({ + CreateSnippetParamsSchema.parse({ ...validRequest, content: undefined, }) @@ -268,15 +273,16 @@ describe("Snippets Management", () => { // Optional fields should work expect(() => - CreateSnippetRequestSchema.parse({ + CreateSnippetParamsSchema.parse({ name: "test", content: "

Test

", }) ).not.toThrow(); }); - it("should validate update snippet request schema", () => { + it("should validate update snippet params schema", () => { const validRequest = { + identifier: "test-snippet", content: "

Updated content {{name}}!

", description: "Updated description", variables: ["name"], @@ -284,12 +290,19 @@ describe("Snippets Management", () => { // Valid request expect(() => - UpdateSnippetRequestSchema.parse(validRequest) + UpdateSnippetParamsSchema.parse(validRequest) ).not.toThrow(); - // Content is required for updates + // Identifier and content are required for updates + expect(() => + UpdateSnippetParamsSchema.parse({ + ...validRequest, + identifier: undefined, + }) + ).toThrow(); + expect(() => - UpdateSnippetRequestSchema.parse({ + UpdateSnippetParamsSchema.parse({ ...validRequest, content: undefined, }) @@ -297,7 +310,10 @@ describe("Snippets Management", () => { // Other fields are optional expect(() => - UpdateSnippetRequestSchema.parse({ content: "

Test

" }) + UpdateSnippetParamsSchema.parse({ + identifier: 12345, + content: "

Test

", + }) ).not.toThrow(); }); }); diff --git a/tests/unit/templates.test.ts b/tests/unit/templates.test.ts index 53d83ac..0da520e 100644 --- a/tests/unit/templates.test.ts +++ b/tests/unit/templates.test.ts @@ -13,8 +13,8 @@ import { EmailTemplateSchema, InAppTemplateSchema, PushTemplateSchema, + SendTemplateProofParamsSchema, SMSTemplateSchema, - TemplateProofRequestSchema, UpdateEmailTemplateParamsSchema, UpsertEmailTemplateParamsSchema, } from "../../src/types/templates.js"; @@ -313,7 +313,7 @@ describe("Template Management", () => { describe("Template proof schema validation", () => { it("should validate proof request with email", () => { - const result = TemplateProofRequestSchema.safeParse(mockProofRequest); + const result = SendTemplateProofParamsSchema.safeParse(mockProofRequest); expect(result.success).toBe(true); }); @@ -323,7 +323,7 @@ describe("Template Management", () => { recipientUserId: "user123", dataFields: { firstName: "Test" }, }; - const result = TemplateProofRequestSchema.safeParse(requestWithUserId); + const result = SendTemplateProofParamsSchema.safeParse(requestWithUserId); expect(result.success).toBe(true); }); @@ -332,7 +332,7 @@ describe("Template Management", () => { templateId: 12345, dataFields: { firstName: "Test" }, }; - const result = TemplateProofRequestSchema.safeParse(invalidRequest); + const result = SendTemplateProofParamsSchema.safeParse(invalidRequest); expect(result.success).toBe(false); }); @@ -340,7 +340,7 @@ describe("Template Management", () => { const invalidRequest = { recipientEmail: "test@example.com", }; - const result = TemplateProofRequestSchema.safeParse(invalidRequest); + const result = SendTemplateProofParamsSchema.safeParse(invalidRequest); expect(result.success).toBe(false); }); });