Skip to content
Merged
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
Original file line number Diff line number Diff line change
Expand Up @@ -125,4 +125,42 @@ describe('generateSecretsSchemaFromSpec', () => {
expect(authTypes).toContain('bearer');
expect(authTypes).toContain('oauth_authorization_code');
});

describe('runtime parse behavior', () => {
test('parses valid secrets for none auth type', () => {
const schema = generateSecretsSchemaFromSpec({ types: ['none'] });
expect(schema.parse({ authType: 'none' })).toEqual({ authType: 'none' });
});

test('parses valid secrets for basic auth type', () => {
const schema = generateSecretsSchemaFromSpec({ types: ['basic'] });
const secrets = { authType: 'basic', username: 'user', password: 'pass' };
expect(schema.parse(secrets)).toEqual(secrets);
});

test('parses valid secrets for bearer auth type', () => {
const schema = generateSecretsSchemaFromSpec({ types: ['bearer'] });
const secrets = { authType: 'bearer', token: 'my-token' };
expect(schema.parse(secrets)).toEqual(secrets);
});

test('throws for invalid authType', () => {
const schema = generateSecretsSchemaFromSpec({ types: ['basic'] });
expect(() =>
schema.parse({ authType: 'invalid_type', username: 'u', password: 'p' })
).toThrow();
});

test('throws for missing required fields in basic auth', () => {
const schema = generateSecretsSchemaFromSpec({ types: ['basic'] });
expect(() => schema.parse({ authType: 'basic', username: 'user' })).toThrow(
/password|Required/
);
});

test('parses empty object when no auth types', () => {
const schema = generateSecretsSchemaFromSpec({ types: [] });
expect(schema.parse({})).toEqual({});
});
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -929,7 +929,6 @@ describe('create()', () => {

test('throws when secrets validation fails', async () => {
const actionType = getConnectorType({
id: 'my-connector-type',
validate: {
secrets: {
schema: z.any().refine(() => {
Expand All @@ -953,6 +952,98 @@ describe('create()', () => {
})
).rejects.toThrow('Secrets validation failed');
});

test('throws when config fails Zod object schema validation', async () => {
const actionType = getConnectorType({
validate: {
config: { schema: z.object({ requiredField: z.string() }) },
secrets: { schema: z.any() },
params: { schema: z.object({}) },
},
});
(actionTypeRegistry.get as jest.Mock).mockReturnValue(actionType);

await expect(
create({
context: mockContext,
action: {
name: 'my name',
actionTypeId: 'my-connector-type',
config: {},
secrets: {},
},
})
).rejects.toThrow(/error validating connector type config/);
});

test('throws when secrets fail Zod object schema validation', async () => {
const actionType = getConnectorType({
validate: {
config: { schema: z.any() },
secrets: { schema: z.object({ apiKey: z.string() }) },
params: { schema: z.object({}) },
},
});
(actionTypeRegistry.get as jest.Mock).mockReturnValue(actionType);

await expect(
create({
context: mockContext,
action: {
name: 'my name',
actionTypeId: 'my-connector-type',
config: {},
secrets: {},
},
})
).rejects.toThrow(/error validating connector type secrets/);
});

test('throws when config has wrong type for Zod schema', async () => {
const actionType = getConnectorType({
validate: {
config: { schema: z.object({ port: z.number() }) },
secrets: { schema: z.any() },
params: { schema: z.object({}) },
},
});
(actionTypeRegistry.get as jest.Mock).mockReturnValue(actionType);

await expect(
create({
context: mockContext,
action: {
name: 'my name',
actionTypeId: 'my-connector-type',
config: { port: 'not-a-number' },
secrets: {},
},
})
).rejects.toThrow(/error validating connector type config/);
});

test('throws when secrets have wrong type for Zod schema', async () => {
const actionType = getConnectorType({
validate: {
config: { schema: z.any() },
secrets: { schema: z.object({ apiKey: z.string() }) },
params: { schema: z.object({}) },
},
});
(actionTypeRegistry.get as jest.Mock).mockReturnValue(actionType);

await expect(
create({
context: mockContext,
action: {
name: 'my name',
actionTypeId: 'my-connector-type',
config: {},
secrets: { apiKey: 12345 },
},
})
).rejects.toThrow(/error validating connector type secrets/);
});
});

describe('deprecated connectors', () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -556,30 +556,46 @@ describe('get()', () => {
});

describe('schema validation', () => {
test('logs warning when connector fails validation but returns connector anyway', async () => {
// authMode is passed through directly from attributes, so an invalid value
// triggers connectorSchema validation failure
const invalidAttributes = {
name: 'Test Connector',
actionTypeId: '.webhook',
config: {},
isMissingSecrets: false,
authMode: 'invalid-auth-mode',
};

test('does not throw when connector validation fails — returns connector anyway', async () => {
getConnectorSoMock.mockResolvedValueOnce({
id: '1',
type: 'action',
attributes: {
name: 'Test Connector',
actionTypeId: '.webhook',
config: {},
isMissingSecrets: false,
},
attributes: invalidAttributes,
references: [],
});

const result = await get({
context: mockContext,
id: '1',
});
const result = await get({ context: mockContext, id: '1' });

expect(result).toBeDefined();
expect(result.id).toBe('1');
expect(result.name).toBe('Test Connector');
});

test('does not throw when connector validation fails', async () => {
test('logs a warning when connector schema validation fails', async () => {
getConnectorSoMock.mockResolvedValueOnce({
id: '1',
type: 'action',
attributes: invalidAttributes,
references: [],
});

await get({ context: mockContext, id: '1' });

expect(logger.warn).toHaveBeenCalledWith(
expect.stringContaining('Error validating connector: 1')
);
});

test('does not log a warning when connector schema validation passes', async () => {
getConnectorSoMock.mockResolvedValueOnce({
id: '1',
type: 'action',
Expand All @@ -592,12 +608,9 @@ describe('get()', () => {
references: [],
});

await expect(
get({
context: mockContext,
id: '1',
})
).resolves.toBeDefined();
await get({ context: mockContext, id: '1' });

expect(logger.warn).not.toHaveBeenCalled();
});
});
});
Loading
Loading