diff --git a/src/audit-logs/audit-logs.spec.ts b/src/audit-logs/audit-logs.spec.ts index d34aab05e..c27dc9cc0 100644 --- a/src/audit-logs/audit-logs.spec.ts +++ b/src/audit-logs/audit-logs.spec.ts @@ -1,6 +1,7 @@ import fetch from 'jest-fetch-mock'; import { UnauthorizedException } from '../common/exceptions'; import { BadRequestException } from '../common/exceptions/bad-request.exception'; +import { ListResponse } from '../common/interfaces'; import { mockWorkOsResponse } from '../common/utils/workos-mock-response'; import { WorkOS } from '../workos'; import { @@ -8,6 +9,7 @@ import { AuditLogExportOptions, AuditLogExportResponse, AuditLogSchema, + AuditLogSchemaResponse, CreateAuditLogEventOptions, CreateAuditLogSchemaOptions, CreateAuditLogSchemaResponse, @@ -844,4 +846,237 @@ describe('AuditLogs', () => { }); }); }); + + describe('listSchemas', () => { + describe('when the api responds with a 200', () => { + it('returns a paginated list of schemas', async () => { + const workosSpy = jest.spyOn(WorkOS.prototype, 'get'); + + const time = new Date().toISOString(); + + const schemaResponse: AuditLogSchemaResponse = { + object: 'audit_log_schema', + version: 1, + targets: [ + { + type: 'user', + metadata: { + type: 'object', + properties: { + user_id: { type: 'string' }, + }, + }, + }, + ], + actor: { + metadata: { + type: 'object', + properties: { + actor_id: { type: 'string' }, + }, + }, + }, + metadata: { + type: 'object', + properties: { + foo: { type: 'number' }, + }, + }, + created_at: time, + }; + + const listResponse: ListResponse = { + object: 'list', + data: [schemaResponse], + list_metadata: { + before: undefined, + after: undefined, + }, + }; + + workosSpy.mockResolvedValueOnce(mockWorkOsResponse(200, listResponse)); + + const workos = new WorkOS('sk_test_Sz3IQjepeSWaI4cMS4ms4sMuU'); + + const result = await workos.auditLogs.listSchemas({ + action: 'user.logged_in', + }); + + expect(result.data).toHaveLength(1); + // Metadata is deserialized to simplified format (same as createSchema) + expect(result.data[0]).toEqual({ + object: 'audit_log_schema', + version: 1, + targets: [ + { + type: 'user', + metadata: { user_id: 'string' }, + }, + ], + actor: { + metadata: { actor_id: 'string' }, + }, + metadata: { foo: 'number' }, + createdAt: time, + }); + + expect(workosSpy).toHaveBeenCalledWith( + '/audit_logs/actions/user.logged_in/schemas', + { query: { order: 'desc' } }, + ); + }); + }); + + describe('with pagination options', () => { + it('passes pagination parameters to the API', async () => { + const workosSpy = jest.spyOn(WorkOS.prototype, 'get'); + + const listResponse: ListResponse = { + object: 'list', + data: [], + list_metadata: { + before: undefined, + after: undefined, + }, + }; + + workosSpy.mockResolvedValueOnce(mockWorkOsResponse(200, listResponse)); + + const workos = new WorkOS('sk_test_Sz3IQjepeSWaI4cMS4ms4sMuU'); + + await workos.auditLogs.listSchemas({ + action: 'user.logged_in', + limit: 10, + after: 'cursor_123', + order: 'asc', + }); + + expect(workosSpy).toHaveBeenCalledWith( + '/audit_logs/actions/user.logged_in/schemas', + { query: { limit: 10, after: 'cursor_123', order: 'asc' } }, + ); + }); + }); + + describe('when the api responds with a 401', () => { + it('throws an UnauthorizedException', async () => { + const workosSpy = jest.spyOn(WorkOS.prototype, 'get'); + + workosSpy.mockImplementationOnce(() => { + throw new UnauthorizedException('a-request-id'); + }); + + const workos = new WorkOS('invalid apikey'); + + await expect( + workos.auditLogs.listSchemas({ action: 'user.logged_in' }), + ).rejects.toThrow(UnauthorizedException); + }); + }); + + describe('with schema without optional fields', () => { + it('returns schema with undefined actor and metadata', async () => { + const workosSpy = jest.spyOn(WorkOS.prototype, 'get'); + + const time = new Date().toISOString(); + + const schemaResponse: AuditLogSchemaResponse = { + object: 'audit_log_schema', + version: 1, + targets: [ + { + type: 'document', + }, + ], + created_at: time, + }; + + const listResponse: ListResponse = { + object: 'list', + data: [schemaResponse], + list_metadata: { + before: undefined, + after: undefined, + }, + }; + + workosSpy.mockResolvedValueOnce(mockWorkOsResponse(200, listResponse)); + + const workos = new WorkOS('sk_test_Sz3IQjepeSWaI4cMS4ms4sMuU'); + + const result = await workos.auditLogs.listSchemas({ + action: 'document.created', + }); + + expect(result.data).toHaveLength(1); + expect(result.data[0]).toEqual({ + object: 'audit_log_schema', + version: 1, + targets: [ + { + type: 'document', + metadata: undefined, + }, + ], + actor: undefined, + metadata: undefined, + createdAt: time, + }); + }); + }); + + describe('with multiple schemas', () => { + it('returns all schemas in the response', async () => { + const workosSpy = jest.spyOn(WorkOS.prototype, 'get'); + + const time1 = new Date().toISOString(); + const time2 = new Date(Date.now() - 1000).toISOString(); + + const schemaResponse1: AuditLogSchemaResponse = { + object: 'audit_log_schema', + version: 2, + targets: [{ type: 'user' }], + created_at: time1, + }; + + const schemaResponse2: AuditLogSchemaResponse = { + object: 'audit_log_schema', + version: 1, + targets: [{ type: 'user' }], + metadata: { + type: 'object', + properties: { + ip_address: { type: 'string' }, + }, + }, + created_at: time2, + }; + + const listResponse: ListResponse = { + object: 'list', + data: [schemaResponse1, schemaResponse2], + list_metadata: { + before: 'cursor_before', + after: 'cursor_after', + }, + }; + + workosSpy.mockResolvedValueOnce(mockWorkOsResponse(200, listResponse)); + + const workos = new WorkOS('sk_test_Sz3IQjepeSWaI4cMS4ms4sMuU'); + + const result = await workos.auditLogs.listSchemas({ + action: 'user.logged_in', + }); + + expect(result.data).toHaveLength(2); + expect(result.data[0].version).toBe(2); + expect(result.data[1].version).toBe(1); + // Metadata is deserialized to simplified format + expect(result.data[1].metadata).toEqual({ ip_address: 'string' }); + expect(result.listMetadata.before).toBe('cursor_before'); + expect(result.listMetadata.after).toBe('cursor_after'); + }); + }); + }); }); diff --git a/src/audit-logs/audit-logs.ts b/src/audit-logs/audit-logs.ts index 105d72e4d..d10df4d86 100644 --- a/src/audit-logs/audit-logs.ts +++ b/src/audit-logs/audit-logs.ts @@ -1,7 +1,11 @@ +import { PaginationOptions } from '../common/interfaces'; +import { fetchAndDeserialize } from '../common/utils/fetch-and-deserialize'; +import { AutoPaginatable } from '../common/utils/pagination'; import { WorkOS } from '../workos'; import { CreateAuditLogEventOptions, CreateAuditLogEventRequestOptions, + ListSchemasOptions, } from './interfaces'; import { AuditLogExportOptions } from './interfaces/audit-log-export-options.interface'; import { @@ -10,16 +14,19 @@ import { } from './interfaces/audit-log-export.interface'; import { AuditLogSchema, - CreateAuditLogSchemaOptions, - CreateAuditLogSchemaRequestOptions, + AuditLogSchemaResponse, +} from './interfaces/audit-log-schema.interface'; +import { CreateAuditLogSchemaResponse, + CreateAuditLogSchemaRequestOptions, + CreateAuditLogSchemaOptions, } from './interfaces/create-audit-log-schema-options.interface'; import { deserializeAuditLogExport, + deserializeAuditLogSchema, serializeAuditLogExportOptions, serializeCreateAuditLogEventOptions, serializeCreateAuditLogSchemaOptions, - deserializeAuditLogSchema, } from './serializers'; export class AuditLogs { @@ -77,4 +84,28 @@ export class AuditLogs { return deserializeAuditLogSchema(data); } + + async listSchemas( + options: ListSchemasOptions, + ): Promise> { + const { action, ...paginationOptions } = options; + const endpoint = `/audit_logs/actions/${action}/schemas`; + + return new AutoPaginatable( + await fetchAndDeserialize( + this.workos, + endpoint, + deserializeAuditLogSchema, + paginationOptions, + ), + (params: PaginationOptions) => + fetchAndDeserialize( + this.workos, + endpoint, + deserializeAuditLogSchema, + params, + ), + options, + ); + } } diff --git a/src/audit-logs/interfaces/audit-log-schema.interface.ts b/src/audit-logs/interfaces/audit-log-schema.interface.ts new file mode 100644 index 000000000..ff2b4ac47 --- /dev/null +++ b/src/audit-logs/interfaces/audit-log-schema.interface.ts @@ -0,0 +1,46 @@ +export type AuditLogSchemaMetadata = + | Record + | undefined; + +export interface AuditLogActorSchema { + metadata: Record; +} + +export interface AuditLogTargetSchema { + type: string; + metadata?: Record; +} + +export interface AuditLogSchema { + object: 'audit_log_schema'; + version: number; + targets: AuditLogTargetSchema[]; + actor: AuditLogActorSchema | undefined; + metadata: Record | undefined; + createdAt: string; +} + +interface SerializedAuditLogTargetSchema { + type: string; + metadata?: { + type: 'object'; + properties: AuditLogSchemaMetadata; + }; +} + +export interface AuditLogSchemaResponse { + object: 'audit_log_schema'; + version: number; + targets: SerializedAuditLogTargetSchema[]; + actor?: { + metadata: { + type: 'object'; + properties: AuditLogSchemaMetadata; + }; + }; + metadata?: { + type: 'object'; + properties: AuditLogSchemaMetadata; + }; + created_at: string; +} diff --git a/src/audit-logs/interfaces/create-audit-log-schema-options.interface.ts b/src/audit-logs/interfaces/create-audit-log-schema-options.interface.ts index 8f87cc02d..c7004e2dd 100644 --- a/src/audit-logs/interfaces/create-audit-log-schema-options.interface.ts +++ b/src/audit-logs/interfaces/create-audit-log-schema-options.interface.ts @@ -1,26 +1,19 @@ import { PostOptions } from '../../common/interfaces'; -export type AuditLogSchemaMetadata = - | Record - | undefined; - -export interface AuditLogSchema { - object: 'audit_log_schema'; - version: number; - targets: AuditLogTargetSchema[]; - actor: AuditLogActorSchema; - metadata: Record | undefined; - createdAt: string; -} - -export interface AuditLogActorSchema { - metadata: Record; -} - -export interface AuditLogTargetSchema { - type: string; - metadata?: Record | undefined; -} +export type { + AuditLogSchema, + AuditLogSchemaResponse, + AuditLogSchemaMetadata, + AuditLogActorSchema, + AuditLogTargetSchema, +} from './audit-log-schema.interface'; + +import type { + AuditLogSchemaMetadata, + AuditLogSchemaResponse, + AuditLogActorSchema, + AuditLogTargetSchema, +} from './audit-log-schema.interface'; export interface CreateAuditLogSchemaOptions { action: string; @@ -51,22 +44,8 @@ export interface SerializedCreateAuditLogSchemaOptions { }; } -export interface CreateAuditLogSchemaResponse { - object: 'audit_log_schema'; - version: number; - targets: SerializedAuditLogTargetSchema[]; - actor: { - metadata: { - type: 'object'; - properties: AuditLogSchemaMetadata; - }; - }; - metadata?: { - type: 'object'; - properties: AuditLogSchemaMetadata; - }; - created_at: string; -} +/** @deprecated Use AuditLogSchemaResponse instead */ +export type CreateAuditLogSchemaResponse = AuditLogSchemaResponse; export type CreateAuditLogSchemaRequestOptions = Pick< PostOptions, diff --git a/src/audit-logs/interfaces/index.ts b/src/audit-logs/interfaces/index.ts index c52752fc2..274427a46 100644 --- a/src/audit-logs/interfaces/index.ts +++ b/src/audit-logs/interfaces/index.ts @@ -1,4 +1,6 @@ export * from './audit-log-export-options.interface'; export * from './audit-log-export.interface'; +export * from './audit-log-schema.interface'; export * from './create-audit-log-event-options.interface'; export * from './create-audit-log-schema-options.interface'; +export * from './list-schemas-options.interface'; diff --git a/src/audit-logs/interfaces/list-schemas-options.interface.ts b/src/audit-logs/interfaces/list-schemas-options.interface.ts new file mode 100644 index 000000000..bae771b7f --- /dev/null +++ b/src/audit-logs/interfaces/list-schemas-options.interface.ts @@ -0,0 +1,4 @@ +import { PaginationOptions } from '../../common/interfaces'; +export interface ListSchemasOptions extends PaginationOptions { + action: string; +} diff --git a/src/audit-logs/serializers/create-audit-log-schema.serializer.ts b/src/audit-logs/serializers/create-audit-log-schema.serializer.ts index 9b21e3dda..e2950040c 100644 --- a/src/audit-logs/serializers/create-audit-log-schema.serializer.ts +++ b/src/audit-logs/serializers/create-audit-log-schema.serializer.ts @@ -1,4 +1,4 @@ -import { AuditLogSchema, CreateAuditLogSchemaResponse } from '../interfaces'; +import { AuditLogSchema, AuditLogSchemaResponse } from '../interfaces'; function deserializeMetadata(metadata: { properties?: Record; @@ -19,21 +19,19 @@ function deserializeMetadata(metadata: { } export const deserializeAuditLogSchema = ( - auditLogSchema: CreateAuditLogSchemaResponse, + auditLogSchema: AuditLogSchemaResponse, ): AuditLogSchema => ({ object: auditLogSchema.object, version: auditLogSchema.version, - targets: auditLogSchema.targets.map((target) => { - return { - type: target.type, - metadata: target.metadata - ? deserializeMetadata(target.metadata) - : undefined, - }; - }), - actor: { - metadata: deserializeMetadata(auditLogSchema.actor?.metadata), - }, + targets: auditLogSchema.targets.map((target) => ({ + type: target.type, + metadata: target.metadata + ? deserializeMetadata(target.metadata) + : undefined, + })), + actor: auditLogSchema.actor + ? { metadata: deserializeMetadata(auditLogSchema.actor.metadata) } + : undefined, metadata: auditLogSchema.metadata ? deserializeMetadata(auditLogSchema.metadata) : undefined,