diff --git a/src/lib/affiliates/validation.test.ts b/src/lib/affiliates/validation.test.ts index 0c67ba4f..ab23da04 100644 --- a/src/lib/affiliates/validation.test.ts +++ b/src/lib/affiliates/validation.test.ts @@ -170,6 +170,15 @@ describe("validateOfferInput", () => { expect(result.errors.some((e) => e.includes("10 tags"))).toBe(true); }); + it("rejects non-string tags before sanitizing", () => { + const result = validateOfferInput({ + ...validInput, + tags: ["valid", 123] as any, + }); + expect(result.ok).toBe(false); + expect(result.errors.some((e) => e.includes("tags") && e.includes("strings"))).toBe(true); + }); + it("rejects title shorter than 3 characters", () => { const result = validateOfferInput({ ...validInput, title: "AB" }); expect(result.ok).toBe(false); diff --git a/src/lib/affiliates/validation.ts b/src/lib/affiliates/validation.ts index e0bc3801..d2091ddb 100644 --- a/src/lib/affiliates/validation.ts +++ b/src/lib/affiliates/validation.ts @@ -121,8 +121,17 @@ export function validateOfferInput(input: OfferInput): ValidationResult { errors.push(`Category must be one of: ${SKILL_CATEGORIES.join(", ")}`); } - if (input.tags && input.tags.length > 10) { - errors.push("Maximum 10 tags"); + if (input.tags !== undefined) { + if (!Array.isArray(input.tags)) { + errors.push("tags must be an array"); + } else { + if (input.tags.length > 10) { + errors.push("Maximum 10 tags"); + } + if (input.tags.some((tag) => typeof tag !== "string")) { + errors.push("tags must be strings"); + } + } } if (errors.length > 0) {