sql-talk
Version:
SQL Talk - 自然言語をSQLに変換するMCPサーバー(安全性保護・SSHトンネル対応) / SQL Talk - MCP Server for Natural Language to SQL conversion with safety guards and SSH tunnel support
133 lines (107 loc) • 4.5 kB
text/typescript
import { describe, it, expect, beforeEach } from 'vitest';
import { PIIMasker } from '@/security/pii-masker.js';
describe('PIIMasker', () => {
let masker: PIIMasker;
beforeEach(() => {
masker = new PIIMasker();
});
describe('PII column detection', () => {
it('should detect common PII column names', () => {
const piiColumns = ['name', 'email', 'phone', 'address', '氏名', '住所'];
const nonPiiColumns = ['id', 'created_at', 'status', 'count'];
piiColumns.forEach(col => {
expect(masker.isPIIColumn(col)).toBe(true);
});
nonPiiColumns.forEach(col => {
expect(masker.isPIIColumn(col)).toBe(false);
});
});
it('should handle mixed case and patterns', () => {
expect(masker.isPIIColumn('user_name')).toBe(true);
expect(masker.isPIIColumn('EMAIL_ADDRESS')).toBe(true);
expect(masker.isPIIColumn('phone_number')).toBe(true);
});
});
describe('Data masking', () => {
it('should mask PII data in query results', () => {
const columns = ['id', 'name', 'email', 'status'];
const rows = [
{ id: 1, name: 'John Doe', email: 'john@example.com', status: 'active' },
{ id: 2, name: 'Jane Smith', email: 'jane@example.com', status: 'inactive' }
];
const result = masker.maskQueryResults(columns, rows);
expect(result.piiDetected).toBe(true);
expect(result.maskedColumns).toContain('name');
expect(result.maskedColumns).toContain('email');
expect(result.maskedColumns).not.toContain('id');
expect(result.maskedColumns).not.toContain('status');
// Check that PII data is actually masked
result.maskedRows.forEach(row => {
expect(row.name).not.toBe('John Doe');
expect(row.name).not.toBe('Jane Smith');
expect(row.email).not.toBe('john@example.com');
expect(row.email).not.toBe('jane@example.com');
// Non-PII data should remain unchanged
expect([1, 2]).toContain(row.id);
expect(['active', 'inactive']).toContain(row.status);
});
});
it('should return original data when no PII detected', () => {
const columns = ['id', 'created_at', 'status'];
const rows = [
{ id: 1, created_at: '2024-01-01', status: 'active' }
];
const result = masker.maskQueryResults(columns, rows);
expect(result.piiDetected).toBe(false);
expect(result.maskedColumns).toHaveLength(0);
expect(result.maskedRows).toEqual(rows);
});
it('should handle null and undefined values', () => {
const columns = ['name'];
const rows = [
{ name: null },
{ name: undefined },
{ name: 'John Doe' }
];
const result = masker.maskQueryResults(columns, rows);
expect(result.maskedRows[0].name).toBeNull();
expect(result.maskedRows[1].name).toBeUndefined();
expect(result.maskedRows[2].name).not.toBe('John Doe');
});
});
describe('SQL PII detection', () => {
it('should detect PII columns in SQL queries', () => {
const sql = 'SELECT id, name, email, phone FROM users WHERE active = true';
const result = masker.detectPIIInSQL(sql);
expect(result.hasPII).toBe(true);
expect(result.piiColumns).toContain('name');
expect(result.piiColumns).toContain('email');
expect(result.piiColumns).toContain('phone');
expect(result.warnings).toHaveLength(3);
});
it('should handle complex SQL with aliases', () => {
const sql = 'SELECT u.id, u.name as user_name, u.email FROM users u';
const result = masker.detectPIIInSQL(sql);
expect(result.hasPII).toBe(true);
expect(result.piiColumns).toContain('name');
expect(result.piiColumns).toContain('email');
});
it('should return no PII for safe queries', () => {
const sql = 'SELECT id, created_at, status FROM orders';
const result = masker.detectPIIInSQL(sql);
expect(result.hasPII).toBe(false);
expect(result.piiColumns).toHaveLength(0);
expect(result.warnings).toHaveLength(0);
});
});
describe('Masking strategies', () => {
it('should support different masking strategies', () => {
masker.setStrategy('hash');
expect(masker.getStrategy()).toBe('hash');
masker.setStrategy('redact');
expect(masker.getStrategy()).toBe('redact');
masker.setStrategy('partial');
expect(masker.getStrategy()).toBe('partial');
});
});
});