UNPKG

@getanthill/datastore

Version:

Event-Sourced Datastore

1,359 lines (1,216 loc) 33.2 kB
import Metabase from './Metabase'; describe('sdk/Metabase', () => { let client; beforeEach(() => { client = new Metabase({ username: 'john', password: 'password', }); client.axios.request = jest.fn().mockImplementation(() => ({ data: {} })); client.setSessionToken('token'); }); afterEach(() => { jest.restoreAllMocks(); }); describe('constructor', () => { it('creates a new client for Metabase', () => { client = new Metabase({ username: 'john', }); expect(client.config).toEqual({ baseUrl: 'http://localhost:3000', username: 'john', }); }); }); describe('#toSnakeCase', () => { it('changes the string format to snake_case', async () => { expect(Metabase.toSnakeCase('User Accounts')).toEqual('user_accounts'); }); }); describe('#toMetabaseDisplayCase', () => { it('changes the string format Metabase display case', async () => { expect(Metabase.toMetabaseDisplayCase('user_accounts')).toEqual( 'User Accounts', ); }); }); describe('#setSessionToken', () => { it('stores the token in client sessionToken', async () => { client = new Metabase({}); client.setSessionToken('token'); expect(client.sessionToken).toEqual('token'); }); it('configures axios to use the session token', async () => { client = new Metabase({}); client.setSessionToken('token'); expect( client.axios.defaults.headers.common['X-Metabase-Session'], ).toEqual('token'); }); }); describe('#authenticated', () => { it('performs an authentication request', async () => { jest.spyOn(client, 'setSessionToken'); await client.authenticate(); expect(client.axios.request).toHaveBeenLastCalledWith({ method: 'post', url: '/api/session', data: { username: 'john', password: 'password', }, }); expect(client.setSessionToken).toHaveBeenCalledTimes(1); }); }); describe('#request', () => { it('performs a request on the client', async () => { await client.request({ method: 'get', url: '/heartbeat', }); expect(client.axios.request).toHaveBeenCalledTimes(1); expect(client.axios.request).toHaveBeenLastCalledWith({ method: 'get', url: '/heartbeat', }); }); it('throws an error in case of unauthenticated client', async () => { let error; client = new Metabase({}); try { await client.request({ method: 'get', url: '/heartbeat', }); } catch (err) { error = err; } expect(error).toEqual(Metabase.ERRORS.UNAUTHENTICATED); }); }); describe('#getCurrentUser', () => { it('retrieves current user', async () => { await client.getCurrentUser(); expect(client.axios.request).toHaveBeenLastCalledWith({ method: 'get', url: '/api/user/current', }); }); }); describe('#getDatabases', () => { it('retrieves all available databases', async () => { await client.getDatabases(); expect(client.axios.request).toHaveBeenLastCalledWith({ method: 'get', url: '/api/database', }); }); }); describe('#getTables', () => { it('returns empty array if not initialized yet', async () => { expect(client.getTables()).toEqual([]); }); it('returns tables if initialized', async () => { client.setTables([{ table_id: 0 }]); expect(client.getTables()).toEqual([{ table_id: 0 }]); }); }); describe('#getTableForeignKeys', () => { it('retrieves foreign keys associated to a table', async () => { await client.getTableForeignKeys(12); expect(client.axios.request).toHaveBeenLastCalledWith({ method: 'get', url: `/api/table/12/fks`, }); }); }); describe('#getTableFields', () => { it('returns empty array on unknown table', async () => { client.setTables([]); const fields = client.getTableFields(13); expect(fields).toEqual([]); }); it('retrieves fields available in a table', async () => { client.setTables([ { id: 12, fields: [ { id: 1, }, ], }, ]); const fields = client.getTableFields(12); expect(fields).toEqual([ { id: 1, }, ]); }); }); describe('#getField', () => { it('retrieves a specific field', async () => { await client.getField(12); expect(client.axios.request).toHaveBeenLastCalledWith({ method: 'get', url: `/api/field/12`, }); }); }); describe('#updateField', () => { it('updates a specific field', async () => { await client.updateField(12, { name: 'another name' }); expect(client.axios.request).toHaveBeenLastCalledWith({ method: 'put', url: `/api/field/12`, data: { name: 'another name' }, }); }); }); describe('#getDatabaseTables', () => { it('retrieves all available tables', async () => { await client.getDatabaseTables(12); expect(client.axios.request).toHaveBeenLastCalledWith({ method: 'get', url: `/api/database/12/metadata`, params: { include_hidden: true, }, }); }); }); describe('#updateTable', () => { it('retrieves a specific field', async () => { await client.updateTable(12, { name: 'another name' }); expect(client.axios.request).toHaveBeenLastCalledWith({ method: 'put', url: `/api/table/12`, data: { name: 'another name' }, }); }); }); describe('#getNormalizedName', () => { it('returns the table name in snakecase', async () => { expect(Metabase.getNormalizedName({ name: 'User Accounts' })).toEqual( 'user_accounts', ); }); it('returns the deep path in snakecase if `nfc_path` field exist', async () => { expect( Metabase.getNormalizedName({ name: 'path', nfc_path: ['a', 'B'] }), ).toEqual('a.b'); }); it('returns the deep path in snakecase if `nfc_path` field exist', async () => { expect( Metabase.getNormalizedName({ name: 'path', nfc_path: ['User Details', 'ID'], }), ).toEqual('user_details.id'); }); it('removes first level `entity` if present', async () => { expect( Metabase.getNormalizedName({ name: 'path', nfc_path: ['entity', 'a', 'B'], }), ).toEqual('a.b'); }); it('removes first level `event` if present', async () => { expect( Metabase.getNormalizedName({ name: 'path', nfc_path: ['event', 'a', 'B'], }), ).toEqual('a.b'); }); }); describe('#getModelNameFromTable', () => { it('returns the model associated to a table', async () => { expect(Metabase.getModelNameFromTable({ name: 'User Accounts' })).toEqual( 'user_accounts', ); }); it('returns the model associated to an events table', async () => { expect( Metabase.getModelNameFromTable({ name: 'User Accounts Events' }), ).toEqual('user_accounts'); }); it('returns the model associated to a snapshots table', async () => { expect( Metabase.getModelNameFromTable({ name: 'User Accounts Snapshots' }), ).toEqual('user_accounts'); }); }); describe('#isEventsTable', () => { it('returns true if the table is an events table', async () => { expect(Metabase.isEventsTable({ name: 'User Accounts Events' })).toEqual( true, ); }); it('returns false if the table is not an events table', async () => { expect(Metabase.isEventsTable({ name: 'User Accounts' })).toEqual(false); }); }); describe('#isSnapshotsTable', () => { it('returns true if the table is a snapshots table', async () => { expect( Metabase.isSnapshotsTable({ name: 'User Accounts Snapshots' }), ).toEqual(true); }); it('returns false if the table is not an events table', async () => { expect(Metabase.isSnapshotsTable({ name: 'User Accounts' })).toEqual( false, ); }); }); describe('#isCorrelationField', () => { it('returns true if the field is a correlation field', async () => { expect( Metabase.isCorrelationField( { name: 'User ID', table_name: 'Users', table_id: 0 }, { name: 'users', correlation_field: 'user_id', }, ), ).toEqual(true); }); it('returns false if the table is not an events table', async () => { expect( Metabase.isCorrelationField( { name: 'Created At', table_name: 'Users', table_id: 0 }, [ { name: 'users', correlation_field: 'user_id', }, ], ), ).toEqual(false); }); }); describe('#isEncryptedField', () => { it('returns true if the field is an encrypted field', async () => { expect( Metabase.isEncryptedField( { name: 'Email', table_name: 'Users', table_id: 0 }, { name: 'users', encrypted_fields: ['email'], }, ), ).toEqual(true); }); it('returns false if the field is not an encrypted field', async () => { expect( Metabase.isEncryptedField( { name: 'Quantity', table_name: 'Users', table_id: 0 }, { name: 'users', encrypted_fields: ['email'], }, ), ).toEqual(false); }); it('returns false if the model does not have any encrypted field', async () => { expect( Metabase.isEncryptedField( { name: 'Email', table_name: 'Users', table_id: 0 }, { name: 'users', }, ), ).toEqual(false); }); }); describe('#getFieldVisibility', () => { it('returns false if the field is an encrypted field', async () => { expect( Metabase.getFieldVisibility( { name: 'Email', table_name: 'Users', table_id: 0 }, { name: 'users', encrypted_fields: ['email'], }, ), ).toEqual('sensitive'); }); it('returns true if the field is a correlation field', async () => { expect( Metabase.getFieldVisibility( { name: 'User ID', table_name: 'Users', table_id: 0 }, { name: 'users', correlation_field: 'user_id', }, ), ).toEqual('normal'); }); it('returns true if the field is a type field on events table', async () => { expect( Metabase.getFieldVisibility( { name: 'Type', table_name: 'Users Events', table_id: 1 }, { name: 'users', correlation_field: 'user_id', }, ), ).toEqual('normal'); }); it('returns true if the field is created_at', async () => { expect( Metabase.getFieldVisibility( { name: 'Created At', table_name: 'Users Events', table_id: 1 }, { name: 'users', correlation_field: 'user_id', }, ), ).toEqual('normal'); }); it('returns true if the field is updated_at', async () => { expect( Metabase.getFieldVisibility( { name: 'Updated At', table_name: 'Users Events', table_id: 1 }, { name: 'users', correlation_field: 'user_id', }, ), ).toEqual('normal'); }); it('returns true if the field is version', async () => { expect( Metabase.getFieldVisibility( { name: 'Version', table_name: 'Users Events', table_id: 1 }, { name: 'users', correlation_field: 'user_id', }, ), ).toEqual('normal'); }); it('returns true if the field is in the model properties', async () => { expect( Metabase.getFieldVisibility( { name: 'Quantity', table_name: 'Users Events', table_id: 1 }, { name: 'users', correlation_field: 'user_id', schema: { model: { properties: { quantity: { type: 'number', }, }, }, }, }, ), ).toEqual('normal'); }); it('returns false if the field is not in the model properties', async () => { expect( Metabase.getFieldVisibility( { name: 'Quantity', table_name: 'Users Events', table_id: 1 }, { name: 'users', correlation_field: 'user_id', }, ), ).toEqual('sensitive'); }); }); describe('#getBaseTypeFromJsonSchema', () => { it('returns the default base type if no other one is found', async () => { expect( Metabase.getBaseTypeFromJsonSchema( { name: 'User ID', table_name: 'Users', table_id: 0 }, { name: 'users', correlation_field: 'user_id', }, { type: 'unknown', }, ), ).toEqual(null); }); it('returns `type/Boolean` for a boolean JSON Schema type', async () => { expect( Metabase.getBaseTypeFromJsonSchema( { name: 'Is Enabled', table_name: 'Users', table_id: 0 }, { name: 'users', correlation_field: 'user_id', }, { type: 'boolean', }, ), ).toEqual('type/Boolean'); }); it('returns `type/Integer` for an integer JSON Schema type', async () => { expect( Metabase.getBaseTypeFromJsonSchema( { name: 'Age', table_name: 'Users', table_id: 0 }, { name: 'users', correlation_field: 'user_id', }, { type: 'integer', }, ), ).toEqual('type/Integer'); }); it('returns `type/Float` for a number JSON Schema type', async () => { expect( Metabase.getBaseTypeFromJsonSchema( { name: 'Price', table_name: 'Users', table_id: 0 }, { name: 'users', correlation_field: 'user_id', }, { type: 'number', }, ), ).toEqual('type/Float'); }); }); describe('#getSemanticTypeFromJsonSchema', () => { it('returns the default base type if no other one is found', async () => { expect( Metabase.getSemanticTypeFromJsonSchema( { name: 'Unknown', table_name: 'Users', table_id: 0 }, { name: 'users', correlation_field: 'user_id', }, { type: 'unknown', }, ), ).toEqual(null); }); it('returns `type/PK` for a correlation field', async () => { expect( Metabase.getSemanticTypeFromJsonSchema( { name: 'User ID', table_name: 'Users', table_id: 0 }, { name: 'users', correlation_field: 'user_id', }, { type: 'number', }, ), ).toEqual('type/PK'); }); it('returns `type/Category` for a type field of events', async () => { expect( Metabase.getSemanticTypeFromJsonSchema( { name: 'Type', table_name: 'Users Events', table_id: 1 }, { name: 'users', correlation_field: 'user_id', }, { type: 'number', }, ), ).toEqual('type/Category'); }); it('returns `type/Name` for a name field', async () => { expect( Metabase.getSemanticTypeFromJsonSchema( { name: 'Name', table_name: 'Users', table_id: 0 }, { name: 'users', correlation_field: 'user_id', }, { type: 'number', }, ), ).toEqual('type/Name'); }); it('returns `type/Description` for a description field', async () => { expect( Metabase.getSemanticTypeFromJsonSchema( { name: 'Description', table_name: 'Users', table_id: 0 }, { name: 'users', correlation_field: 'user_id', }, { type: 'number', }, ), ).toEqual('type/Description'); }); it('returns `type/CreationTimestamp` for a creatd_at field', async () => { expect( Metabase.getSemanticTypeFromJsonSchema( { name: 'Created At', table_name: 'Users', table_id: 0 }, { name: 'users', correlation_field: 'user_id', }, { type: 'number', }, ), ).toEqual('type/CreationTimestamp'); }); it('returns `type/UpdatedTimestamp` for a updated_at field', async () => { expect( Metabase.getSemanticTypeFromJsonSchema( { name: 'Updated At', table_name: 'Users', table_id: 0 }, { name: 'users', correlation_field: 'user_id', }, { type: 'number', }, ), ).toEqual('type/UpdatedTimestamp'); }); it('returns `type/Category` for a boolean field type', async () => { expect( Metabase.getSemanticTypeFromJsonSchema( { name: 'Is Enabled', table_name: 'Users', table_id: 0 }, { name: 'users', correlation_field: 'user_id', }, { type: 'boolean', }, ), ).toEqual('type/Category'); }); }); describe('#setFields', () => { it('stores fields in the client', async () => { client.setFields([ { name: 'users', }, ]); expect(client.getFields()).toEqual([ { name: 'users', }, ]); }); }); describe('#getFields', () => { it('returns an empty array if not set already', async () => { expect(client.getFields()).toEqual([]); }); it('returns fields available in the client', async () => { client.setFields([ { name: 'users', }, ]); expect(client.getFields()).toEqual([ { name: 'users', }, ]); }); }); describe('#setModels', () => { it('stores models in the client', async () => { client.setModels([ { name: 'users', }, ]); expect(client.getModels()).toEqual([ { name: 'users', }, ]); }); }); describe('#getModels', () => { it('returns an empty array if not set already', async () => { expect(client.getModels()).toEqual([]); }); it('returns models available in the client', async () => { client.setModels([ { name: 'users', }, ]); expect(client.getModels()).toEqual([ { name: 'users', }, ]); }); }); describe('#setGraph', () => { it('stores a graph in the client', async () => { client.setGraph({ nodes: [], edges: [ { source: '1', target: '2', }, ], }); expect(client.getGraph()).toEqual({ nodes: [], edges: [ { source: '1', target: '2', }, ], }); }); }); describe('#getGraph', () => { it('returns an empty graph if not set already', async () => { expect(client.getGraph()).toEqual({ nodes: [], edges: [], }); }); it('returns models available in the client', async () => { client.setGraph({ nodes: [], edges: [ { source: '1', target: '2', }, ], }); expect(client.getGraph()).toEqual({ nodes: [], edges: [ { source: '1', target: '2', }, ], }); }); }); describe('#getModelFromTable', () => { it('returns the model associated to a table', async () => { client.setModels([ { name: 'users', }, ]); expect(client.getModelFromTable({ name: 'Users' })).toEqual({ name: 'users', }); }); }); describe('#getTableUpdatePayload', () => { it('returns null for field not available in the models', () => { client.setModels([]); expect(client.getTableUpdatePayload({ name: 'Users' })).toEqual(null); }); it('returns a table update payload for a standard table', () => { client.setModels([ { name: 'users', description: 'Users available in our platform', }, ]); expect(client.getTableUpdatePayload({ name: 'Users' })).toEqual({ name: 'users', display_name: 'users', description: 'Users available in our platform', visibility_type: null, }); }); it('returns a table update payload for a standard table without description', () => { client.setModels([ { name: 'users', }, ]); expect(client.getTableUpdatePayload({ name: 'Users' })).toEqual({ name: 'users', display_name: 'users', description: null, visibility_type: null, }); }); it('returns a table update payload with hidden visibility for snapshots tables', () => { client.setModels([ { name: 'users', description: 'Users available in our platform', }, ]); expect(client.getTableUpdatePayload({ name: 'Users Snapshots' })).toEqual( { name: 'users_snapshots', display_name: 'users_snapshots', description: 'Users available in our platform', visibility_type: 'hidden', }, ); }); it('returns a table update payload with description adjusted if an events table', () => { client.setModels([ { name: 'users', description: 'Users available in our platform', }, ]); expect(client.getTableUpdatePayload({ name: 'Users Events' })).toEqual({ name: 'users_events', display_name: 'users_events', description: 'Events associated to these entities. Users available in our platform', visibility_type: null, }); }); it('returns a table update payload with description adjusted if an events table which does not have model description', () => { client.setModels([ { name: 'users', }, ]); expect(client.getTableUpdatePayload({ name: 'Users Events' })).toEqual({ name: 'users_events', display_name: 'users_events', description: 'Events associated to these entities. ', visibility_type: null, }); }); }); describe('#getFieldUpdatePayload', () => { beforeEach(() => { client.setTables([ { id: 0, name: 'Users', }, ]); }); it('returns null for table not available in the tables', () => { expect( client.getFieldUpdatePayload({ name: 'Email', table_id: -1 }), ).toEqual(null); }); it('returns null for field not available in the models', () => { expect( client.getFieldUpdatePayload({ name: 'Email', table_id: 0 }), ).toEqual(null); }); it('returns a field update payload for a standard field', () => { client.setModels([ { name: 'users', description: 'Users available in our platform', schema: { model: { properties: { email: { type: 'string', description: 'User email address', }, }, }, }, }, ]); expect( client.getFieldUpdatePayload({ name: 'Email', table_id: 0 }), ).toEqual({ name: 'email', display_name: 'email', description: 'User email address', base_type: null, semantic_type: null, visibility_type: 'normal', }); }); it('returns a field update payload for a non defined field', () => { client.setModels([ { name: 'users', description: 'Users available in our platform', }, ]); expect( client.getFieldUpdatePayload({ name: 'Email', table_id: 0 }), ).toEqual({ name: 'email', display_name: 'email', description: null, base_type: null, semantic_type: null, visibility_type: 'sensitive', }); }); it('returns a field update payload for a correlation field', () => { client.setModels([ { name: 'users', description: 'Users available in our platform', correlation_field: 'user_id', }, ]); expect( client.getFieldUpdatePayload({ name: 'User ID', table_id: 0 }), ).toEqual({ name: 'user_id', display_name: 'user_id', description: 'Correlation field', base_type: null, semantic_type: 'type/PK', visibility_type: 'normal', }); }); it('returns a field update payload for a encrypted sensitive field', () => { client.setModels([ { name: 'users', description: 'Users available in our platform', encrypted_fields: ['email'], schema: { model: { properties: { email: { type: 'string', description: 'User email address', }, }, }, }, }, ]); expect( client.getFieldUpdatePayload({ name: 'Email', table_id: 0 }), ).toEqual({ name: 'email', display_name: 'email', description: 'User email address', base_type: null, semantic_type: null, visibility_type: 'sensitive', }); }); }); describe('#getFieldUpdatePayloadWithForeignKeys', () => { it('returns a field update payload for a field without any relationship', () => { client.setModels([ { name: 'users', }, ]); client.setTables([ { id: 1, name: 'Users Events', }, ]); expect( client.getFieldUpdatePayloadWithForeignKeys( { name: 'Email', table_id: 1 }, { name: 'email', }, ), ).toEqual({ name: 'email', }); }); it('returns a field update payload without any change for a correlation field but without field found', () => { client.setModels([ { name: 'users', correlation_field: 'user_id', }, ]); client.setTables([ { id: 1, name: 'Users Events', }, ]); expect( client.getFieldUpdatePayloadWithForeignKeys( { name: 'User ID', table_: 1 }, { name: 'email', }, ), ).toEqual({ name: 'email', }); }); it('noops on unknown table', () => { expect( client.getFieldUpdatePayloadWithForeignKeys( { name: 'User ID', table_id: -1 }, { name: 'email', }, ), ).toEqual({ name: 'email', }); }); it('noops on unknown model', () => { client.setTables([ { id: 1, name: 'Users Events', }, ]); expect( client.getFieldUpdatePayloadWithForeignKeys( { name: 'User ID', table_id: 1 }, { name: 'email', }, ), ).toEqual({ name: 'email', }); }); it('returns a field update payload for a correlation field on events table', () => { client.setModels([ { name: 'users', correlation_field: 'user_id', }, ]); client.setTables([ { id: 0, name: 'Users', }, { id: 1, name: 'Users Events', }, ]); client.setFields([ { id: 1, name: 'user_id', table_id: 0, table_name: 'Users', }, ]); expect( client.getFieldUpdatePayloadWithForeignKeys( { name: 'User ID', table_id: 1 }, { name: 'email', }, ), ).toEqual({ name: 'email', fk_target_field_id: 1, semantic_type: 'type/FK', }); }); it('returns a non updated payload if events table and correlation field not found on entities', () => { client.setModels([ { name: 'users', correlation_field: 'user_id', }, ]); client.setTables([ { id: 0, name: 'Users', }, { id: 1, name: 'Users Events', }, ]); client.setFields([]); expect( client.getFieldUpdatePayloadWithForeignKeys( { name: 'User ID', table_id: 1 }, { name: 'email', }, ), ).toEqual({ name: 'email', }); }); it('returns a field update payload unchanged for a foreign correlation field but without found edge', () => { client.setModels([ { name: 'users', correlation_field: 'another_id', }, { name: 'devices', correlation_field: 'device_id', }, ]); client.setGraph({ edges: [ { source: 'devices', target: 'users', key: 'user_id', correlation_field: 'user_id', }, ], }); client.setTables([ { id: 9, name: 'users', }, { id: 10, name: 'devices', }, ]); client.setFields([ { id: 0, table_id: 9, name: 'user_id', table_name: 'users', }, { id: 1, table_id: 10, name: 'device_id', table_name: 'devices', }, ]); expect( client.getFieldUpdatePayloadWithForeignKeys( { name: 'Device ID', table_id: 10 }, { name: 'device_id', }, ), ).toEqual({ name: 'device_id', }); }); it('returns a field update payload unchanged for a foreign correlation field but without found target correlation field', () => { client.setModels([ { name: 'users', correlation_field: 'another_id', }, { name: 'devices', correlation_field: 'device_id', }, ]); client.setGraph({ edges: [ { source: 'devices', target: 'users', key: 'user_id', correlation_field: 'user_id', }, ], }); client.setTables([ { id: 9, name: 'users', }, { id: 10, name: 'devices', }, ]); client.setFields([ { id: 1, table_id: 10, name: 'device_id', table_name: 'devices', }, ]); expect( client.getFieldUpdatePayloadWithForeignKeys( { name: 'User ID', table_id: 10 }, { name: 'user_id', }, ), ).toEqual({ name: 'user_id', }); }); it('returns a field update payload for a foreign correlation field', () => { client.setModels([ { name: 'users', correlation_field: 'user_id', }, ]); client.setGraph({ edges: [ { source: 'users', target: 'devices', key: 'device_id', correlation_field: 'device_id', }, ], }); client.setTables([ { id: 9, name: 'users', }, { id: 10, name: 'devices', }, ]); client.setFields([ { id: 1, table_id: 10, name: 'device_id', table_name: 'devices', }, ]); expect( client.getFieldUpdatePayloadWithForeignKeys( { name: 'Device ID', table_id: 9 }, { name: 'device_id', }, ), ).toEqual({ name: 'device_id', fk_target_field_id: 1, semantic_type: 'type/FK', }); }); }); });