File size: 3,685 Bytes
aa89cb5
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
import { SelectQuery } from '../src/core/ast';
import { UserContext } from '../src/core/context';
import { EntitySchema, TextDiagnosticSQLLogSink } from '../src/sql/core';
import { MySQLDriver, MySQLTeaQLClient } from '../src/sql/mysql';
import { PostgreSQLDriver, PostgreSQLTeaQLClient } from '../src/sql/postgres';

const cases = [
  ['PostgreSQL', 'TEAQL_TEST_POSTGRES_URL',
    (url: string, schemas: Record<string, EntitySchema>) => new PostgreSQLTeaQLClient(url, schemas),
    (url: string) => new PostgreSQLDriver(url)],
  ['MySQL', 'TEAQL_TEST_MYSQL_URL',
    (url: string, schemas: Record<string, EntitySchema>) => new MySQLTeaQLClient(url, schemas),
    (url: string) => new MySQLDriver(url)],
] as const;

describe.each(cases)('%s live SQL masking', (name, envName, createClient, createDriver) => {
  const url = process.env[envName]?.trim();
  const required = process.env.TEAQL_REQUIRE_LIVE_DB?.toLowerCase() === 'true';
  const testIfConfigured = url || required ? test : test.skip;

  testIfConfigured('preserves execution values while rendering field-aware Q and mutation logs', async () => {
    if (!url) throw new Error(`${envName} is required for live provider tests`);
    const table = `teaql_mask_${name.toLowerCase()}_${process.pid}_${Date.now()}`;
    const schemas: Record<string, EntitySchema> = { Customer: {
      table,
      auditMaskFields: ['display_name', 'password_hash'],
      columns: {
        id: { columnName: 'id', modelName: 'id', logicalType: 'integer', decode: 'string' },
        version: { columnName: 'version', modelName: 'version', logicalType: 'integer', decode: 'number' },
        displayName: { columnName: 'display_name', modelName: 'display_name', logicalType: 'text', decode: 'native' },
        publicAddress: { columnName: 'public_address', modelName: 'public_address', logicalType: 'text', decode: 'native', logPolicy: 'plain' },
        passwordHash: { columnName: 'password_hash', modelName: 'password_hash', logicalType: 'text', decode: 'native' },
      },
    } };
    const lines: string[] = [];
    const client = createClient(url!, schemas)
      .setDiagnosticSQLLogSink(new TextDiagnosticSQLLogSink(line => lines.push(line)));
    const context = new UserContext().insertResource('dataService', client);
    const cleanupDriver = createDriver(url!);
    try {
      await context.ensureSchema();
      await client.executeMutation({
        entity: 'Customer', action: 'Create', id: '1',
        payload: { display_name: 'Riverside', public_address: '1 Runtime Road', password_hash: 'PASSWORD-CANARY' },
        comment: 'what: create a masked customer',
      });
      const rows = await client.executeQuery(new SelectQuery('Customer')
        .filter({ displayName: { $eq: 'Riverside' }, publicAddress: { $eq: '1 Runtime Road' } })
        .limit(1)
        .comment('what: read a masked customer')
        .purpose('why: verify live-provider SQL masking'));
      expect(rows).toHaveLength(1);
      expect(rows[0].displayName).toBe('Riverside');
      expect(rows[0].publicAddress).toBe('1 Runtime Road');
      const logged = lines.join('\n');
      expect(logged).toContain('Ri*****de');
      expect(logged).toContain('1 Runtime Road');
      expect(logged).toContain('what: read a masked customer');
      expect(logged).toContain('why: verify live-provider SQL masking');
      expect(logged).toContain('SELECT');
      expect(logged).not.toContain('Riverside');
      expect(logged).not.toContain('PASSWORD-CANARY');
    } finally {
      await client.close();
      await cleanupDriver.query(`DROP TABLE IF EXISTS ${cleanupDriver.identifier(table)}`);
      await cleanupDriver.close();
    }
  });
});