Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
228 changes: 228 additions & 0 deletions src/audit-logs/audit-logs.spec.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,15 @@
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 {
AuditLogExport,
AuditLogExportOptions,
AuditLogExportResponse,
AuditLogSchema,
AuditLogSchemaResponse,
CreateAuditLogEventOptions,
CreateAuditLogSchemaOptions,
CreateAuditLogSchemaResponse,
Expand Down Expand Up @@ -844,4 +846,230 @@ 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<AuditLogSchemaResponse> = {
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('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<AuditLogSchemaResponse> = {
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('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('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<AuditLogSchemaResponse> = {
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('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<AuditLogSchemaResponse> = {
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('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');
});
});
});
});
36 changes: 33 additions & 3 deletions src/audit-logs/audit-logs.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
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,
Expand All @@ -10,16 +13,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 {
Expand Down Expand Up @@ -77,4 +83,28 @@ export class AuditLogs {

return deserializeAuditLogSchema(data);
}

async listSchemas(
action: string,
options?: PaginationOptions,
): Promise<AutoPaginatable<AuditLogSchema, PaginationOptions>> {
const endpoint = `/audit_logs/actions/${action}/schemas`;

return new AutoPaginatable(
await fetchAndDeserialize<AuditLogSchemaResponse, AuditLogSchema>(
this.workos,
endpoint,
deserializeAuditLogSchema,
options,
),
(params: PaginationOptions) =>
fetchAndDeserialize<AuditLogSchemaResponse, AuditLogSchema>(
this.workos,
endpoint,
deserializeAuditLogSchema,
params,
),
options,
);
}
}
46 changes: 46 additions & 0 deletions src/audit-logs/interfaces/audit-log-schema.interface.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
export type AuditLogSchemaMetadata =
| Record<string, { type: 'string' | 'boolean' | 'number' }>
| undefined;

export interface AuditLogActorSchema {
metadata: Record<string, string | boolean | number>;
}

export interface AuditLogTargetSchema {
type: string;
metadata?: Record<string, string | boolean | number>;
}

export interface AuditLogSchema {
object: 'audit_log_schema';
version: number;
targets: AuditLogTargetSchema[];
actor: AuditLogActorSchema | undefined;
metadata: Record<string, string | boolean | number> | undefined;
createdAt: string;
}

interface SerializedAuditLogTargetSchema {
type: string;
metadata?: {
type: 'object';
properties: AuditLogSchemaMetadata;
};
}

export interface AuditLogSchemaResponse {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If this is exported publicly, this would be a breaking change. Perhaps we can alias CreateAuditLogSchemaResponse to AuditLogSchemaResponse and mark CreateAuditLogSchemaResponse as deprecated?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@mattgd updated. Added the alias and also leaving all the exports in CreateAuditLogSchemaResponse as is and just exporting the types from the common, new audit log interface i made.

object: 'audit_log_schema';
version: number;
targets: SerializedAuditLogTargetSchema[];
actor?: {
metadata: {
type: 'object';
properties: AuditLogSchemaMetadata;
};
};
metadata?: {
type: 'object';
properties: AuditLogSchemaMetadata;
};
created_at: string;
}
Loading