const {
  ValidationError,
  validateDNSRecord,
  validateDockerDeployment,
  validateFilePath,
  validateVolumePath,
  validateURL,
  validateToken,
  validateServiceConfig,
  sanitizeString,
  isValidPort,
  isPrivateIP
} = require('../input-validator');

// Helper: extract .errors from ValidationError
function getErrors(fn) {
  try {
    fn();
    return null;
  } catch (e) {
    return e;
  }
}

describe('ValidationError', () => {
  test('creates error with message and field', () => {
    const err = new ValidationError('bad input', 'name');
    expect(err.message).toBe('bad input');
    expect(err.field).toBe('name');
  });

  test('has statusCode 400', () => {
    expect(new ValidationError('x').statusCode).toBe(400);
  });

  test('has name "ValidationError"', () => {
    expect(new ValidationError('x').name).toBe('ValidationError');
  });

  test('defaults field to null', () => {
    expect(new ValidationError('x').field).toBeNull();
  });

  test('is instance of Error', () => {
    expect(new ValidationError('x')).toBeInstanceOf(Error);
  });
});

describe('validateDNSRecord', () => {
  const valid = { subdomain: 'myapp', ip: '192.168.1.1' };

  describe('valid inputs', () => {
    test('accepts valid subdomain and ip', () => {
      const result = validateDNSRecord(valid);
      expect(result.subdomain).toBe('myapp');
      expect(result.ip).toBe('192.168.1.1');
    });

    test('returns sanitized lowercase output', () => {
      const result = validateDNSRecord({ subdomain: 'MyApp', ip: '10.0.0.1' });
      expect(result.subdomain).toBe('myapp');
    });

    test('defaults ttl to 3600 when not provided', () => {
      expect(validateDNSRecord(valid).ttl).toBe(3600);
    });

    test('accepts explicit ttl', () => {
      expect(validateDNSRecord({ ...valid, ttl: 300 }).ttl).toBe(300);
    });

    test('accepts IPv6 addresses', () => {
      const result = validateDNSRecord({ subdomain: 'test', ip: '::1' });
      expect(result.ip).toBe('::1');
    });

    test('accepts valid domain', () => {
      const result = validateDNSRecord({ ...valid, domain: 'example.local' });
      expect(result.domain).toBe('example.local');
    });

    test('returns null domain when not provided', () => {
      expect(validateDNSRecord(valid).domain).toBeNull();
    });

    test('lowercases and trims subdomain in output', () => {
      const result = validateDNSRecord({ subdomain: 'MyApp', ip: '10.0.0.1' });
      expect(result.subdomain).toBe('myapp');
    });
  });

  describe('subdomain validation', () => {
    test('rejects missing subdomain', () => {
      const err = getErrors(() => validateDNSRecord({ ip: '1.2.3.4' }));
      expect(err).toBeInstanceOf(ValidationError);
    });

    test('rejects non-string subdomain', () => {
      const err = getErrors(() => validateDNSRecord({ subdomain: 123, ip: '1.2.3.4' }));
      expect(err).toBeInstanceOf(ValidationError);
    });

    test('rejects subdomain starting with hyphen', () => {
      const err = getErrors(() => validateDNSRecord({ subdomain: '-bad', ip: '1.2.3.4' }));
      expect(err).toBeInstanceOf(ValidationError);
    });

    test('rejects subdomain ending with hyphen', () => {
      const err = getErrors(() => validateDNSRecord({ subdomain: 'bad-', ip: '1.2.3.4' }));
      expect(err).toBeInstanceOf(ValidationError);
    });

    test('accepts single-character subdomain', () => {
      expect(validateDNSRecord({ subdomain: 'a', ip: '1.2.3.4' }).subdomain).toBe('a');
    });

    test('accepts subdomain with hyphens in middle', () => {
      expect(validateDNSRecord({ subdomain: 'my-app', ip: '1.2.3.4' }).subdomain).toBe('my-app');
    });

    test('rejects subdomain exceeding 63 characters', () => {
      const long = 'a'.repeat(64);
      const err = getErrors(() => validateDNSRecord({ subdomain: long, ip: '1.2.3.4' }));
      expect(err).toBeInstanceOf(ValidationError);
    });
  });

  describe('injection prevention', () => {
    const chars = [';', '&', '|', '`', '$', '(', ')', '<', '>', '\n', '\r', '\\'];
    chars.forEach(char => {
      test(`rejects "${char === '\n' ? '\\n' : char === '\r' ? '\\r' : char}" in subdomain`, () => {
        const err = getErrors(() => validateDNSRecord({ subdomain: `test${char}bad`, ip: '1.2.3.4' }));
        expect(err).toBeInstanceOf(ValidationError);
      });
    });
  });

  describe('IP validation', () => {
    test('rejects missing IP', () => {
      const err = getErrors(() => validateDNSRecord({ subdomain: 'test' }));
      expect(err).toBeInstanceOf(ValidationError);
    });

    test('rejects invalid IP format', () => {
      const err = getErrors(() => validateDNSRecord({ subdomain: 'test', ip: '999.999.999.999' }));
      expect(err).toBeInstanceOf(ValidationError);
    });

    test('rejects non-string IP', () => {
      const err = getErrors(() => validateDNSRecord({ subdomain: 'test', ip: 12345 }));
      expect(err).toBeInstanceOf(ValidationError);
    });

    test('blocks private IP when blockPrivateIPs is true', () => {
      const err = getErrors(() => validateDNSRecord({ subdomain: 'test', ip: '192.168.1.1', blockPrivateIPs: true }));
      expect(err).toBeInstanceOf(ValidationError);
    });

    test('allows private IP when blockPrivateIPs is absent', () => {
      expect(validateDNSRecord({ subdomain: 'test', ip: '192.168.1.1' }).ip).toBe('192.168.1.1');
    });
  });

  describe('TTL validation', () => {
    test('rejects TTL below 60', () => {
      const err = getErrors(() => validateDNSRecord({ ...valid, ttl: 10 }));
      expect(err).toBeInstanceOf(ValidationError);
    });

    test('rejects TTL above 86400', () => {
      const err = getErrors(() => validateDNSRecord({ ...valid, ttl: 100000 }));
      expect(err).toBeInstanceOf(ValidationError);
    });

    test('rejects non-numeric TTL', () => {
      const err = getErrors(() => validateDNSRecord({ ...valid, ttl: 'abc' }));
      expect(err).toBeInstanceOf(ValidationError);
    });

    test('accepts TTL at lower boundary (60)', () => {
      expect(validateDNSRecord({ ...valid, ttl: 60 }).ttl).toBe(60);
    });

    test('accepts TTL at upper boundary (86400)', () => {
      expect(validateDNSRecord({ ...valid, ttl: 86400 }).ttl).toBe(86400);
    });
  });

  describe('error aggregation', () => {
    test('returns multiple errors for multiple invalid fields', () => {
      const err = getErrors(() => validateDNSRecord({ ttl: 1 }));
      expect(err.errors.length).toBeGreaterThan(1);
    });

    test('throws ValidationError with .errors array', () => {
      const err = getErrors(() => validateDNSRecord({}));
      expect(err).toBeInstanceOf(ValidationError);
      expect(Array.isArray(err.errors)).toBe(true);
    });
  });
});

describe('validateDockerDeployment', () => {
  const valid = { name: 'myapp', image: 'nginx:latest' };

  describe('valid inputs', () => {
    test('accepts valid name and image', () => {
      const result = validateDockerDeployment(valid);
      expect(result.name).toBe('myapp');
      expect(result.image).toBe('nginx:latest');
    });

    test('returns defaults for optional fields', () => {
      const result = validateDockerDeployment(valid);
      expect(result.ports).toEqual([]);
      expect(result.volumes).toEqual([]);
      expect(result.environment).toEqual({});
    });
  });

  describe('container name validation', () => {
    test('rejects missing name', () => {
      const err = getErrors(() => validateDockerDeployment({ image: 'nginx' }));
      expect(err).toBeInstanceOf(ValidationError);
    });

    test('rejects name starting with special char', () => {
      const err = getErrors(() => validateDockerDeployment({ name: '-bad', image: 'nginx' }));
      expect(err).toBeInstanceOf(ValidationError);
    });

    test('rejects name exceeding 255 characters', () => {
      const err = getErrors(() => validateDockerDeployment({ name: 'a'.repeat(256), image: 'nginx' }));
      expect(err).toBeInstanceOf(ValidationError);
    });

    test('accepts name with underscores, periods, hyphens', () => {
      const result = validateDockerDeployment({ name: 'my_app.v1-test', image: 'nginx' });
      expect(result.name).toBe('my_app.v1-test');
    });
  });

  describe('image validation', () => {
    test('rejects missing image', () => {
      const err = getErrors(() => validateDockerDeployment({ name: 'app' }));
      expect(err).toBeInstanceOf(ValidationError);
    });

    test('accepts simple image', () => {
      expect(validateDockerDeployment({ name: 'a', image: 'alpine' }).image).toBe('alpine');
    });

    test('accepts image with tag', () => {
      expect(validateDockerDeployment({ name: 'a', image: 'nginx:latest' }).image).toBe('nginx:latest');
    });

    test('accepts fully qualified image', () => {
      const result = validateDockerDeployment({ name: 'a', image: 'docker.io/library/nginx:1.21' });
      expect(result.image).toBe('docker.io/library/nginx:1.21');
    });

    test('rejects image with semicolon', () => {
      const err = getErrors(() => validateDockerDeployment({ name: 'a', image: 'nginx;rm -rf /' }));
      expect(err).toBeInstanceOf(ValidationError);
    });

    test('rejects image with $( subshell', () => {
      const err = getErrors(() => validateDockerDeployment({ name: 'a', image: 'nginx$(evil)' }));
      expect(err).toBeInstanceOf(ValidationError);
    });

    test('rejects image exceeding 512 characters', () => {
      const err = getErrors(() => validateDockerDeployment({ name: 'a', image: 'a'.repeat(513) }));
      expect(err).toBeInstanceOf(ValidationError);
    });
  });

  describe('ports validation', () => {
    test('rejects non-array ports', () => {
      const err = getErrors(() => validateDockerDeployment({ ...valid, ports: 'bad' }));
      expect(err).toBeInstanceOf(ValidationError);
    });

    test('accepts string port format "8080:80"', () => {
      const result = validateDockerDeployment({ ...valid, ports: ['8080:80'] });
      expect(result.ports).toEqual(['8080:80']);
    });

    test('accepts port format with protocol "8080:80/tcp"', () => {
      const result = validateDockerDeployment({ ...valid, ports: ['8080:80/tcp'] });
      expect(result.ports).toEqual(['8080:80/tcp']);
    });

    test('rejects invalid port format', () => {
      const err = getErrors(() => validateDockerDeployment({ ...valid, ports: ['bad'] }));
      expect(err).toBeInstanceOf(ValidationError);
    });

    test('rejects port numbers > 65535', () => {
      const err = getErrors(() => validateDockerDeployment({ ...valid, ports: ['70000:80'] }));
      expect(err).toBeInstanceOf(ValidationError);
    });

    test('rejects port numbers < 1', () => {
      const err = getErrors(() => validateDockerDeployment({ ...valid, ports: ['0:80'] }));
      expect(err).toBeInstanceOf(ValidationError);
    });

    test('accepts numeric port values', () => {
      const result = validateDockerDeployment({ ...valid, ports: [8080] });
      expect(result.ports).toEqual([8080]);
    });

    test('rejects non-string non-number port values', () => {
      const err = getErrors(() => validateDockerDeployment({ ...valid, ports: [{}] }));
      expect(err).toBeInstanceOf(ValidationError);
    });
  });

  describe('volumes validation', () => {
    test('rejects non-array volumes', () => {
      const err = getErrors(() => validateDockerDeployment({ ...valid, volumes: 'bad' }));
      expect(err).toBeInstanceOf(ValidationError);
    });

    test('rejects non-string volume entries', () => {
      const err = getErrors(() => validateDockerDeployment({ ...valid, volumes: [123] }));
      expect(err).toBeInstanceOf(ValidationError);
    });
  });

  describe('environment validation', () => {
    test('rejects non-object environment', () => {
      const err = getErrors(() => validateDockerDeployment({ ...valid, environment: 'bad' }));
      expect(err).toBeInstanceOf(ValidationError);
    });

    test('rejects array as environment', () => {
      const err = getErrors(() => validateDockerDeployment({ ...valid, environment: [] }));
      expect(err).toBeInstanceOf(ValidationError);
    });

    test('rejects invalid env var names', () => {
      const err = getErrors(() => validateDockerDeployment({ ...valid, environment: { '1BAD': 'val' } }));
      expect(err).toBeInstanceOf(ValidationError);
    });

    test('accepts valid env var names', () => {
      const result = validateDockerDeployment({ ...valid, environment: { MY_VAR: 'test', _under: '1' } });
      expect(result.environment).toEqual({ MY_VAR: 'test', _under: '1' });
    });

    test('accepts string, number, boolean values', () => {
      const env = { A: 'str', B: 42, C: true };
      const result = validateDockerDeployment({ ...valid, environment: env });
      expect(result.environment).toEqual(env);
    });

    test('rejects object values', () => {
      const err = getErrors(() => validateDockerDeployment({ ...valid, environment: { X: { nested: true } } }));
      expect(err).toBeInstanceOf(ValidationError);
    });
  });
});

describe('validateFilePath', () => {
  const isWindows = process.platform === 'win32';

  test('rejects empty path', () => {
    expect(() => validateFilePath('')).toThrow(ValidationError);
  });

  test('rejects null path', () => {
    expect(() => validateFilePath(null)).toThrow(ValidationError);
  });

  test('rejects path with ~', () => {
    expect(() => validateFilePath('~/secrets')).toThrow(ValidationError);
  });

  // On Windows, path.normalize resolves '..' so the normalized path may not contain '..'
  // On Linux, '/app/../etc/passwd' normalizes to '/etc/passwd' which is blocked
  test('blocks C:\\Windows path', () => {
    expect(() => validateFilePath('C:\\Windows\\System32')).toThrow(ValidationError);
  });

  test('blocks C:\\Program Files path', () => {
    expect(() => validateFilePath('C:\\Program Files\\test')).toThrow(ValidationError);
  });

  if (!isWindows) {
    test('rejects path with ..', () => {
      expect(() => validateFilePath('/app/../etc/passwd')).toThrow(ValidationError);
    });

    test('blocks /etc path', () => {
      expect(() => validateFilePath('/etc/passwd')).toThrow(ValidationError);
    });

    test('blocks /proc path', () => {
      expect(() => validateFilePath('/proc/self/environ')).toThrow(ValidationError);
    });

    test('blocks /sys path', () => {
      expect(() => validateFilePath('/sys/class')).toThrow(ValidationError);
    });

    test('blocks /root path', () => {
      expect(() => validateFilePath('/root/.ssh')).toThrow(ValidationError);
    });

    test('blocks /var/run path', () => {
      expect(() => validateFilePath('/var/run/docker.sock')).toThrow(ValidationError);
    });

    test('blocks /var/lib/docker path', () => {
      expect(() => validateFilePath('/var/lib/docker/containers')).toThrow(ValidationError);
    });
  }

  test('returns normalized path for valid input', () => {
    const testPath = isWindows ? 'D:\\app\\data\\config' : '/app/data/config';
    const result = validateFilePath(testPath);
    expect(result).toBeTruthy();
  });

  test('enforces allowedBasePaths when specified', () => {
    const testPath = isWindows ? 'D:\\app\\data' : '/app/data';
    const allowedBase = isWindows ? 'D:\\opt' : '/opt';
    expect(() => validateFilePath(testPath, [allowedBase])).toThrow(ValidationError);
  });

  test('accepts path within allowedBasePaths', () => {
    const testPath = isWindows ? 'D:\\opt\\myapp\\config' : '/opt/myapp/config';
    const allowedBase = isWindows ? 'D:\\opt' : '/opt';
    const result = validateFilePath(testPath, [allowedBase]);
    expect(result).toBeTruthy();
  });

  test('accepts any path when allowedBasePaths is empty', () => {
    const testPath = isWindows ? 'D:\\app\\data' : '/app/data';
    const result = validateFilePath(testPath, []);
    expect(result).toBeTruthy();
  });
});

describe('validateVolumePath', () => {
  test('rejects invalid volume format', () => {
    const errors = validateVolumePath('not-a-volume', 0);
    expect(errors.length).toBeGreaterThan(0);
  });

  test('rejects container path with ..', () => {
    const errors = validateVolumePath('/app/data:/../etc:ro', 0);
    expect(errors.length).toBeGreaterThan(0);
  });

  test('accepts valid modes: ro, rw, z, Z', () => {
    ['ro', 'rw', 'z', 'Z'].forEach(mode => {
      const errors = validateVolumePath(`/app/data:/container/path:${mode}`, 0);
      // Filter to only mode-related errors
      const modeErrors = errors.filter(e => e.field && e.field.includes('mode'));
      expect(modeErrors).toHaveLength(0);
    });
  });

  test('accepts valid volume without mode', () => {
    const errors = validateVolumePath('/app/data:/container/path', 0);
    // Should have no container path errors
    const containerErrors = errors.filter(e => e.field && e.field.includes('containerPath'));
    expect(containerErrors).toHaveLength(0);
  });
});

describe('validateURL', () => {
  test('rejects empty URL', () => {
    expect(() => validateURL('')).toThrow(ValidationError);
  });

  test('rejects null URL', () => {
    expect(() => validateURL(null)).toThrow(ValidationError);
  });

  test('accepts valid https URL', () => {
    expect(validateURL('https://example.com')).toBe('https://example.com');
  });

  test('accepts valid http URL', () => {
    expect(validateURL('http://example.com')).toBe('http://example.com');
  });

  test('rejects non-URL strings', () => {
    expect(() => validateURL('not a url')).toThrow(ValidationError);
  });

  test('blocks localhost when blockPrivate is true', () => {
    expect(() => validateURL('http://localhost:3000', { blockPrivate: true })).toThrow(ValidationError);
  });

  test('blocks 127.0.0.1 when blockPrivate is true', () => {
    expect(() => validateURL('http://127.0.0.1:3000', { blockPrivate: true })).toThrow(ValidationError);
  });

  test('blocks private IPs when blockPrivate is true', () => {
    expect(() => validateURL('http://192.168.1.1', { blockPrivate: true })).toThrow(ValidationError);
  });

  test('allows private IPs when blockPrivate is false', () => {
    expect(validateURL('http://192.168.1.1')).toBe('http://192.168.1.1');
  });
});

describe('validateToken', () => {
  test('rejects empty token', () => {
    expect(() => validateToken('')).toThrow(ValidationError);
  });

  test('rejects null token', () => {
    expect(() => validateToken(null)).toThrow(ValidationError);
  });

  test('rejects token shorter than 8 chars', () => {
    expect(() => validateToken('short')).toThrow(ValidationError);
  });

  test('rejects token longer than 512 chars', () => {
    expect(() => validateToken('a'.repeat(513))).toThrow(ValidationError);
  });

  test('rejects token with semicolon', () => {
    expect(() => validateToken('token123;evil')).toThrow(ValidationError);
  });

  test('rejects token with $( subshell', () => {
    expect(() => validateToken('token123$(evil)')).toThrow(ValidationError);
  });

  test('rejects token with &&', () => {
    expect(() => validateToken('token123&&evil')).toThrow(ValidationError);
  });

  test('accepts valid alphanumeric token', () => {
    expect(validateToken('abcdef12345678')).toBe('abcdef12345678');
  });

  test('trims whitespace', () => {
    expect(validateToken('  abcdef12345678  ')).toBe('abcdef12345678');
  });

  test('accepts token at minimum length (8)', () => {
    expect(validateToken('12345678')).toBe('12345678');
  });

  test('accepts token at maximum length (512)', () => {
    const token = 'a'.repeat(512);
    expect(validateToken(token)).toBe(token);
  });
});

describe('validateServiceConfig', () => {
  const valid = { id: 'my-service', name: 'My Service' };

  test('accepts valid service config', () => {
    const result = validateServiceConfig(valid);
    expect(result.id).toBe('my-service');
  });

  test('rejects missing ID', () => {
    const err = getErrors(() => validateServiceConfig({ name: 'Test' }));
    expect(err).toBeInstanceOf(ValidationError);
  });

  test('rejects invalid ID format', () => {
    const err = getErrors(() => validateServiceConfig({ id: 'bad id!', name: 'Test' }));
    expect(err).toBeInstanceOf(ValidationError);
  });

  test('rejects missing name', () => {
    const err = getErrors(() => validateServiceConfig({ id: 'test' }));
    expect(err).toBeInstanceOf(ValidationError);
  });

  test('rejects name exceeding 100 chars', () => {
    const err = getErrors(() => validateServiceConfig({ id: 'test', name: 'a'.repeat(101) }));
    expect(err).toBeInstanceOf(ValidationError);
  });

  test('validates URL when present', () => {
    const err = getErrors(() => validateServiceConfig({ ...valid, url: 'not a url' }));
    expect(err).toBeInstanceOf(ValidationError);
  });

  test('validates port when present', () => {
    const err = getErrors(() => validateServiceConfig({ ...valid, port: 99999 }));
    expect(err).toBeInstanceOf(ValidationError);
  });

  test('accepts valid URL and port', () => {
    const result = validateServiceConfig({ ...valid, url: 'http://example.com', port: 8080 });
    expect(result.id).toBe('my-service');
  });

  test('aggregates multiple errors', () => {
    const err = getErrors(() => validateServiceConfig({}));
    expect(err.errors.length).toBeGreaterThan(1);
  });
});

describe('isValidPort', () => {
  test('accepts port 1', () => {
    expect(isValidPort(1)).toBe(true);
  });

  test('accepts port 65535', () => {
    expect(isValidPort(65535)).toBe(true);
  });

  test('rejects port 0', () => {
    expect(isValidPort(0)).toBe(false);
  });

  test('rejects port 65536', () => {
    expect(isValidPort(65536)).toBe(false);
  });

  test('accepts string port "8080"', () => {
    expect(isValidPort('8080')).toBe(true);
  });

  test('rejects NaN', () => {
    expect(isValidPort('abc')).toBe(false);
  });

  test('rejects negative port', () => {
    expect(isValidPort(-1)).toBe(false);
  });
});

describe('isPrivateIP', () => {
  test('detects 10.x.x.x as private', () => {
    expect(isPrivateIP('10.0.0.1')).toBe(true);
    expect(isPrivateIP('10.255.255.255')).toBe(true);
  });

  test('detects 172.16-31.x.x as private', () => {
    expect(isPrivateIP('172.16.0.1')).toBe(true);
    expect(isPrivateIP('172.31.255.255')).toBe(true);
  });

  test('does not flag 172.15.x.x as private', () => {
    expect(isPrivateIP('172.15.0.1')).toBe(false);
  });

  test('does not flag 172.32.x.x as private', () => {
    expect(isPrivateIP('172.32.0.1')).toBe(false);
  });

  test('detects 192.168.x.x as private', () => {
    expect(isPrivateIP('192.168.1.1')).toBe(true);
  });

  test('detects 127.x.x.x as private', () => {
    expect(isPrivateIP('127.0.0.1')).toBe(true);
    expect(isPrivateIP('127.255.255.255')).toBe(true);
  });

  test('detects 169.254.x.x as private', () => {
    expect(isPrivateIP('169.254.1.1')).toBe(true);
  });

  test('detects ::1 as private', () => {
    expect(isPrivateIP('::1')).toBe(true);
  });

  test('detects fc00: as private', () => {
    expect(isPrivateIP('fc00::1')).toBe(true);
  });

  test('detects fe80: as private', () => {
    expect(isPrivateIP('fe80::1')).toBe(true);
  });

  test('identifies 8.8.8.8 as public', () => {
    expect(isPrivateIP('8.8.8.8')).toBe(false);
  });

  test('identifies 1.1.1.1 as public', () => {
    expect(isPrivateIP('1.1.1.1')).toBe(false);
  });
});

describe('sanitizeString', () => {
  test('escapes < to &lt;', () => {
    expect(sanitizeString('<script>')).toBe('&lt;script&gt;');
  });

  test('escapes > to &gt;', () => {
    expect(sanitizeString('a>b')).toBe('a&gt;b');
  });

  test('escapes single quote to &#39;', () => {
    expect(sanitizeString("it's")).toBe('it&#39;s');
  });

  test('escapes double quote to &quot;', () => {
    expect(sanitizeString('say "hi"')).toBe('say &quot;hi&quot;');
  });

  test('truncates to maxLength', () => {
    expect(sanitizeString('hello world', 5)).toBe('hello');
  });

  test('returns empty string for non-string input', () => {
    expect(sanitizeString(123)).toBe('');
    expect(sanitizeString(null)).toBe('');
    expect(sanitizeString(undefined)).toBe('');
  });

  test('uses default maxLength of 1000', () => {
    const long = 'a'.repeat(1500);
    expect(sanitizeString(long).length).toBe(1000);
  });

  test('returns safe strings unchanged', () => {
    expect(sanitizeString('hello world')).toBe('hello world');
  });
});
