Skip to main content
Glama
privacy-manager.property.spec.ts•8.96 kB
/** * Property-based tests for PrivacyManager * Feature: mcp-screenshot, Property 18: PII detection accuracy * Validates: Requirements 6.1 */ import * as fc from "fast-check"; import sharp from "sharp"; import { PrivacyManager } from "./privacy-manager"; describe("PrivacyManager - Property-Based Tests", () => { let privacyManager: PrivacyManager; beforeEach(() => { privacyManager = new PrivacyManager(); }); afterEach(async () => { await privacyManager.cleanup(); }); /** * Feature: mcp-screenshot, Property 18: PII detection accuracy * For any image containing known PII patterns (emails, phones, credit cards), * when PII masking is enabled, those patterns should be detected and masked. * Validates: Requirements 6.1 */ describe("Property 18: PII detection accuracy", () => { it("should handle email addresses in images without corruption", async () => { await fc.assert( fc.asyncProperty( fc.emailAddress().filter((email) => { // Filter for emails that are more likely to be detected by OCR const parts = email.split("@"); return ( parts[0].length >= 3 && parts[1].length >= 5 && !/[._-]{2,}/.test(email) ); }), async (email) => { // Create an image with the email text const imageBuffer = await createImageWithText(email, 60); // Apply PII masking const { maskedBuffer, stats } = await privacyManager.maskPII( imageBuffer ); // The masking should not corrupt the image expect(maskedBuffer).toBeInstanceOf(Buffer); expect(maskedBuffer.length).toBeGreaterThan(0); // Stats should be non-negative expect(stats.emailsRedacted).toBeGreaterThanOrEqual(0); expect(stats.phonesRedacted).toBeGreaterThanOrEqual(0); expect(stats.creditCardsRedacted).toBeGreaterThanOrEqual(0); expect(stats.customPatternsRedacted).toBeGreaterThanOrEqual(0); } ), { numRuns: 10 } // Reduced runs due to OCR overhead ); }, 60000); // Increased timeout for OCR it("should handle phone numbers in images without corruption", async () => { await fc.assert( fc.asyncProperty( fc.tuple( fc.integer({ min: 200, max: 999 }), fc.integer({ min: 200, max: 999 }), fc.integer({ min: 1000, max: 9999 }) ), async ([area, prefix, line]) => { const phone = `${area}-${prefix}-${line}`; // Create an image with the phone number const imageBuffer = await createImageWithText(phone, 60); // Apply PII masking const { maskedBuffer, stats } = await privacyManager.maskPII( imageBuffer ); // The masking should not corrupt the image expect(maskedBuffer).toBeInstanceOf(Buffer); expect(maskedBuffer.length).toBeGreaterThan(0); // Stats should be non-negative expect(stats.emailsRedacted).toBeGreaterThanOrEqual(0); expect(stats.phonesRedacted).toBeGreaterThanOrEqual(0); expect(stats.creditCardsRedacted).toBeGreaterThanOrEqual(0); expect(stats.customPatternsRedacted).toBeGreaterThanOrEqual(0); } ), { numRuns: 10 } ); }, 60000); it("should handle credit card numbers in images without corruption", async () => { await fc.assert( fc.asyncProperty( fc.tuple( fc.integer({ min: 1000, max: 9999 }), fc.integer({ min: 1000, max: 9999 }), fc.integer({ min: 1000, max: 9999 }), fc.integer({ min: 1000, max: 9999 }) ), async ([part1, part2, part3, part4]) => { const creditCard = `${part1}-${part2}-${part3}-${part4}`; // Create an image with the credit card number const imageBuffer = await createImageWithText(creditCard, 60); // Apply PII masking const { maskedBuffer, stats } = await privacyManager.maskPII( imageBuffer ); // The masking should not corrupt the image expect(maskedBuffer).toBeInstanceOf(Buffer); expect(maskedBuffer.length).toBeGreaterThan(0); // Stats should be non-negative expect(stats.emailsRedacted).toBeGreaterThanOrEqual(0); expect(stats.phonesRedacted).toBeGreaterThanOrEqual(0); expect(stats.creditCardsRedacted).toBeGreaterThanOrEqual(0); expect(stats.customPatternsRedacted).toBeGreaterThanOrEqual(0); } ), { numRuns: 10 } ); }, 60000); it("should handle custom patterns without corruption", async () => { await fc.assert( fc.asyncProperty( fc .string({ minLength: 5, maxLength: 10 }) .filter((s) => /^[A-Z0-9]+$/.test(s)), async (customText) => { // Create an image with custom text const imageBuffer = await createImageWithText(customText, 60); // Apply PII masking with custom pattern const { maskedBuffer, stats } = await privacyManager.maskPII( imageBuffer, [customText] ); // The masking should not corrupt the image expect(maskedBuffer).toBeInstanceOf(Buffer); expect(maskedBuffer.length).toBeGreaterThan(0); // Stats should be non-negative expect(stats.emailsRedacted).toBeGreaterThanOrEqual(0); expect(stats.phonesRedacted).toBeGreaterThanOrEqual(0); expect(stats.creditCardsRedacted).toBeGreaterThanOrEqual(0); expect(stats.customPatternsRedacted).toBeGreaterThanOrEqual(0); } ), { numRuns: 10 } ); }, 60000); it("should correctly identify PII patterns in detected text", async () => { // Test the pattern matching logic directly with known text await fc.assert( fc.asyncProperty( fc.emailAddress(), fc.tuple( fc.integer({ min: 200, max: 999 }), fc.integer({ min: 200, max: 999 }), fc.integer({ min: 1000, max: 9999 }) ), fc.tuple( fc.integer({ min: 1000, max: 9999 }), fc.integer({ min: 1000, max: 9999 }), fc.integer({ min: 1000, max: 9999 }), fc.integer({ min: 1000, max: 9999 }) ), async (email, [area, prefix, line], [cc1, cc2, cc3, cc4]) => { const phone = `${area}-${prefix}-${line}`; const creditCard = `${cc1}-${cc2}-${cc3}-${cc4}`; const text = `Contact: ${email} Phone: ${phone} Card: ${creditCard}`; // Create image with all PII types const imageBuffer = await createImageWithText(text, 48); // Apply PII masking const { stats, maskedBuffer } = await privacyManager.maskPII( imageBuffer ); // Verify that the stats are non-negative (OCR may or may not detect) expect(stats.emailsRedacted).toBeGreaterThanOrEqual(0); expect(stats.phonesRedacted).toBeGreaterThanOrEqual(0); expect(stats.creditCardsRedacted).toBeGreaterThanOrEqual(0); expect(stats.customPatternsRedacted).toBeGreaterThanOrEqual(0); // Total redactions should be non-negative const total = stats.emailsRedacted + stats.phonesRedacted + stats.creditCardsRedacted + stats.customPatternsRedacted; expect(total).toBeGreaterThanOrEqual(0); // Image should not be corrupted expect(maskedBuffer).toBeInstanceOf(Buffer); expect(maskedBuffer.length).toBeGreaterThan(0); } ), { numRuns: 5 } // Fewer runs for this comprehensive test ); }, 90000); }); }); /** * Helper function to create an image with text */ async function createImageWithText( text: string, fontSize: number = 48 ): Promise<Buffer> { const width = 800; const height = 200; // Escape XML special characters const escapedText = text .replace(/&/g, "&amp;") .replace(/</g, "&lt;") .replace(/>/g, "&gt;") .replace(/"/g, "&quot;") .replace(/'/g, "&apos;"); // Create SVG with text const svg = ` <svg width="${width}" height="${height}"> <rect width="${width}" height="${height}" fill="white"/> <text x="50" y="${ height / 2 }" font-family="Arial" font-size="${fontSize}" fill="black"> ${escapedText} </text> </svg> `; // Convert SVG to PNG buffer const buffer = await sharp(Buffer.from(svg)).png().toBuffer(); return buffer; }

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/Digital-Defiance/mcp-screenshot'

If you have feedback or need assistance with the MCP directory API, please join our Discord server