mirror of
https://github.com/logto-io/logto.git
synced 2025-01-20 21:32:31 -05:00
test(core): guard sign-in methods and social connector ids (#498)
* test(core): sign-in-methods guard of sign-in experience * test(core): social-sign-in-connector-ids guard of sign-in experience * test(core): guard branding of sign-in experience
This commit is contained in:
parent
8cc6a1d138
commit
e969e15e3e
3 changed files with 322 additions and 111 deletions
|
@ -0,0 +1,122 @@
|
|||
import { BrandingStyle, CreateSignInExperience, SignInExperience } from '@logto/schemas';
|
||||
|
||||
import { mockBranding, mockSignInExperience } from '@/utils/mock';
|
||||
import { createRequester } from '@/utils/test-utils';
|
||||
|
||||
import signInExperiencesRoutes from './sign-in-experience';
|
||||
|
||||
jest.mock('@/queries/sign-in-experience', () => ({
|
||||
updateDefaultSignInExperience: jest.fn(
|
||||
async (data: Partial<CreateSignInExperience>): Promise<SignInExperience> => ({
|
||||
...mockSignInExperience,
|
||||
...data,
|
||||
})
|
||||
),
|
||||
}));
|
||||
|
||||
const signInExperienceRequester = createRequester({ authedRoutes: signInExperiencesRoutes });
|
||||
|
||||
const expectPatchResponseStatus = async (signInExperience: any, status: number) => {
|
||||
const response = await signInExperienceRequester.patch('/sign-in-exp').send(signInExperience);
|
||||
expect(response.status).toEqual(status);
|
||||
};
|
||||
|
||||
describe('branding', () => {
|
||||
const colorKeys = ['primaryColor', 'backgroundColor', 'darkPrimaryColor', 'darkBackgroundColor'];
|
||||
|
||||
const invalidColors = [
|
||||
undefined,
|
||||
null,
|
||||
'',
|
||||
'#',
|
||||
'#1',
|
||||
'#2B',
|
||||
'#3cZ',
|
||||
'#4D9e',
|
||||
'#5f80E',
|
||||
'#6GHiXY',
|
||||
'#78Cb5dA',
|
||||
'rgb(0,13,255)',
|
||||
];
|
||||
|
||||
const validColors = ['#aB3', '#169deF'];
|
||||
|
||||
describe('colors', () => {
|
||||
test.each(validColors)('%p should succeed', async (validColor) => {
|
||||
for (const colorKey of colorKeys) {
|
||||
// eslint-disable-next-line no-await-in-loop
|
||||
await expectPatchResponseStatus(
|
||||
{ branding: { ...mockBranding, [colorKey]: validColor } },
|
||||
200
|
||||
);
|
||||
}
|
||||
});
|
||||
test.each(invalidColors)('%p should fail', async (invalidColor) => {
|
||||
for (const colorKey of colorKeys) {
|
||||
// eslint-disable-next-line no-await-in-loop
|
||||
await expectPatchResponseStatus(
|
||||
{ branding: { ...mockBranding, [colorKey]: invalidColor } },
|
||||
400
|
||||
);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
describe('style', () => {
|
||||
test.each(Object.values(BrandingStyle))('%p should succeed', async (style) => {
|
||||
const signInExperience = { branding: { ...mockBranding, style } };
|
||||
await expectPatchResponseStatus(signInExperience, 200);
|
||||
});
|
||||
|
||||
test.each([undefined, '', 'invalid'])('%p should fail', async (style) => {
|
||||
const signInExperience = { branding: { ...mockBranding, style } };
|
||||
await expectPatchResponseStatus(signInExperience, 400);
|
||||
});
|
||||
});
|
||||
|
||||
describe('logoUrl', () => {
|
||||
test.each(['http://silverhand.com/silverhand.png', 'https://logto.dev/logto.jpg'])(
|
||||
'%p should success',
|
||||
async (logoUrl) => {
|
||||
const signInExperience = { branding: { ...mockBranding, logoUrl } };
|
||||
await expectPatchResponseStatus(signInExperience, 200);
|
||||
}
|
||||
);
|
||||
|
||||
test.each([undefined, null, '', 'invalid'])('%p should fail', async (logoUrl) => {
|
||||
const signInExperience = { branding: { ...mockBranding, logoUrl } };
|
||||
await expectPatchResponseStatus(signInExperience, 400);
|
||||
});
|
||||
});
|
||||
|
||||
describe('slogan', () => {
|
||||
test.each([undefined, 'Silverhand.', 'Supercharge innovations.'])(
|
||||
'%p should success',
|
||||
async (slogan) => {
|
||||
const signInExperience = {
|
||||
branding: {
|
||||
...mockBranding,
|
||||
style: BrandingStyle.Logo,
|
||||
slogan,
|
||||
},
|
||||
};
|
||||
await expectPatchResponseStatus(signInExperience, 200);
|
||||
}
|
||||
);
|
||||
|
||||
test.each([null, ''])('%p should fail', async (slogan) => {
|
||||
const signInExperience = {
|
||||
branding: {
|
||||
...mockBranding,
|
||||
style: BrandingStyle.Logo,
|
||||
slogan,
|
||||
},
|
||||
};
|
||||
await expectPatchResponseStatus(signInExperience, 400);
|
||||
});
|
||||
});
|
||||
|
||||
it('should succeed when branding is valid', async () => {
|
||||
await expectPatchResponseStatus({ branding: mockBranding }, 200);
|
||||
});
|
||||
});
|
|
@ -1,10 +1,35 @@
|
|||
import { BrandingStyle, CreateSignInExperience, Language, SignInExperience } from '@logto/schemas';
|
||||
import {
|
||||
CreateSignInExperience,
|
||||
Language,
|
||||
SignInExperience,
|
||||
SignInMethodState,
|
||||
} from '@logto/schemas';
|
||||
|
||||
import { mockBranding, mockLanguageInfo, mockSignInExperience, mockTermsOfUse } from '@/utils/mock';
|
||||
import {
|
||||
mockAliyunDmConnectorInstance,
|
||||
mockAliyunSmsConnectorInstance,
|
||||
mockFacebookConnectorInstance,
|
||||
mockGithubConnectorInstance,
|
||||
mockGoogleConnectorInstance,
|
||||
mockLanguageInfo,
|
||||
mockSignInExperience,
|
||||
mockSignInMethods,
|
||||
mockTermsOfUse,
|
||||
} from '@/utils/mock';
|
||||
import { createRequester } from '@/utils/test-utils';
|
||||
|
||||
import signInExperiencesRoutes from './sign-in-experience';
|
||||
|
||||
jest.mock('@/connectors', () => ({
|
||||
getConnectorInstances: jest.fn(async () => [
|
||||
mockAliyunDmConnectorInstance,
|
||||
mockAliyunSmsConnectorInstance,
|
||||
mockFacebookConnectorInstance,
|
||||
mockGithubConnectorInstance,
|
||||
mockGoogleConnectorInstance,
|
||||
]),
|
||||
}));
|
||||
|
||||
jest.mock('@/queries/sign-in-experience', () => ({
|
||||
updateDefaultSignInExperience: jest.fn(
|
||||
async (data: Partial<CreateSignInExperience>): Promise<SignInExperience> => ({
|
||||
|
@ -24,109 +49,6 @@ const expectPatchResponseStatus = async (signInExperience: any, status: number)
|
|||
const validBooleans = [true, false];
|
||||
const invalidBooleans = [undefined, null, 0, 1, '0', '1', 'true', 'false'];
|
||||
|
||||
describe('branding', () => {
|
||||
const colorKeys = ['primaryColor', 'backgroundColor', 'darkPrimaryColor', 'darkBackgroundColor'];
|
||||
|
||||
const invalidColors = [
|
||||
undefined,
|
||||
null,
|
||||
'',
|
||||
'#',
|
||||
'#1',
|
||||
'#2B',
|
||||
'#3cZ',
|
||||
'#4D9e',
|
||||
'#5f80E',
|
||||
'#6GHiXY',
|
||||
'#78Cb5dA',
|
||||
'rgb(0,13,255)',
|
||||
];
|
||||
|
||||
const validColors = ['#aB3', '#169deF'];
|
||||
|
||||
describe('colors', () => {
|
||||
test.each(validColors)('%p should succeed', async (validColor) => {
|
||||
for (const colorKey of colorKeys) {
|
||||
// eslint-disable-next-line no-await-in-loop
|
||||
await expectPatchResponseStatus(
|
||||
{ branding: { ...mockBranding, [colorKey]: validColor } },
|
||||
200
|
||||
);
|
||||
}
|
||||
});
|
||||
test.each(invalidColors)('%p should fail', async (invalidColor) => {
|
||||
for (const colorKey of colorKeys) {
|
||||
// eslint-disable-next-line no-await-in-loop
|
||||
await expectPatchResponseStatus(
|
||||
{ branding: { ...mockBranding, [colorKey]: invalidColor } },
|
||||
400
|
||||
);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
describe('style', () => {
|
||||
test.each(Object.values(BrandingStyle))('%p should succeed', async (style) => {
|
||||
const signInExperience = { branding: { ...mockBranding, style } };
|
||||
await expectPatchResponseStatus(signInExperience, 200);
|
||||
});
|
||||
|
||||
test.each([undefined, '', 'invalid'])('%p should fail', async (style) => {
|
||||
const signInExperience = { branding: { ...mockBranding, style } };
|
||||
await expectPatchResponseStatus(signInExperience, 400);
|
||||
});
|
||||
});
|
||||
|
||||
describe('logoUrl', () => {
|
||||
test.each(['http://silverhand.com/silverhand.png', 'https://logto.dev/logto.jpg'])(
|
||||
'%p should success',
|
||||
async (logoUrl) => {
|
||||
const signInExperience = { branding: { ...mockBranding, logoUrl } };
|
||||
await expectPatchResponseStatus(signInExperience, 200);
|
||||
}
|
||||
);
|
||||
|
||||
test.each([undefined, null, '', 'invalid'])('%p should fail', async (logoUrl) => {
|
||||
const signInExperience = { branding: { ...mockBranding, logoUrl } };
|
||||
await expectPatchResponseStatus(signInExperience, 400);
|
||||
});
|
||||
});
|
||||
|
||||
describe('slogan', () => {
|
||||
test.each([undefined, 'Silverhand.', 'Supercharge innovations.'])(
|
||||
'%p should success',
|
||||
async (slogan) => {
|
||||
const signInExperience = {
|
||||
branding: {
|
||||
...mockBranding,
|
||||
style: BrandingStyle.Logo,
|
||||
slogan,
|
||||
},
|
||||
};
|
||||
await expectPatchResponseStatus(signInExperience, 200);
|
||||
}
|
||||
);
|
||||
|
||||
test.each([null, ''])('%p should fail', async (slogan) => {
|
||||
const signInExperience = {
|
||||
branding: {
|
||||
...mockBranding,
|
||||
style: BrandingStyle.Logo,
|
||||
slogan,
|
||||
},
|
||||
};
|
||||
await expectPatchResponseStatus(signInExperience, 400);
|
||||
});
|
||||
});
|
||||
|
||||
it('should succeed when branding is valid', async () => {
|
||||
const response = signInExperienceRequester
|
||||
.patch('/sign-in-exp')
|
||||
.send({ branding: mockBranding });
|
||||
await expect(response).resolves.toMatchObject({ status: 200 });
|
||||
});
|
||||
});
|
||||
|
||||
describe('terms of use', () => {
|
||||
describe('enabled', () => {
|
||||
test.each(validBooleans)('%p should success', async (enabled) => {
|
||||
|
@ -197,12 +119,167 @@ describe('languageInfo', () => {
|
|||
});
|
||||
});
|
||||
|
||||
describe('socialSignInConnectorIds', () => {
|
||||
it('should throw when the type of social connector IDs is wrong', async () => {
|
||||
const socialSignInConnectorIds = [123, 456];
|
||||
const response = await signInExperienceRequester.patch('/sign-in-exp').send({
|
||||
socialSignInConnectorIds,
|
||||
describe('signInMethods', () => {
|
||||
const validSignInMethodStates = Object.values(SignInMethodState);
|
||||
const invalidSignInMethodStates = [undefined, null, '', ' \t\n\r', 'invalid'];
|
||||
|
||||
describe('username', () => {
|
||||
test.each(validSignInMethodStates)('%p should success', async (state) => {
|
||||
if (state === SignInMethodState.primary) {
|
||||
return;
|
||||
}
|
||||
const signInExperience = {
|
||||
signInMethods: {
|
||||
username: state,
|
||||
email: SignInMethodState.primary,
|
||||
sms: SignInMethodState.disabled,
|
||||
social: SignInMethodState.disabled,
|
||||
},
|
||||
};
|
||||
await expectPatchResponseStatus(signInExperience, 200);
|
||||
});
|
||||
|
||||
test.each(invalidSignInMethodStates)('%p should fail', async (state) => {
|
||||
if (state === SignInMethodState.primary) {
|
||||
return;
|
||||
}
|
||||
const signInExperience = {
|
||||
signInMethods: {
|
||||
username: state,
|
||||
email: SignInMethodState.primary,
|
||||
sms: SignInMethodState.disabled,
|
||||
social: SignInMethodState.disabled,
|
||||
},
|
||||
};
|
||||
await expectPatchResponseStatus(signInExperience, 400);
|
||||
});
|
||||
});
|
||||
|
||||
describe('email', () => {
|
||||
test.each(validSignInMethodStates)('%p should success', async (state) => {
|
||||
if (state === SignInMethodState.primary) {
|
||||
return;
|
||||
}
|
||||
const signInExperience = {
|
||||
signInMethods: {
|
||||
username: SignInMethodState.disabled,
|
||||
email: state,
|
||||
sms: SignInMethodState.primary,
|
||||
social: SignInMethodState.disabled,
|
||||
},
|
||||
};
|
||||
await expectPatchResponseStatus(signInExperience, 200);
|
||||
});
|
||||
|
||||
test.each(invalidSignInMethodStates)('%p should fail', async (state) => {
|
||||
if (state === SignInMethodState.primary) {
|
||||
return;
|
||||
}
|
||||
const signInExperience = {
|
||||
signInMethods: {
|
||||
username: SignInMethodState.disabled,
|
||||
email: state,
|
||||
sms: SignInMethodState.primary,
|
||||
social: SignInMethodState.disabled,
|
||||
},
|
||||
};
|
||||
await expectPatchResponseStatus(signInExperience, 400);
|
||||
});
|
||||
});
|
||||
|
||||
describe('sms', () => {
|
||||
test.each(validSignInMethodStates)('%p should success', async (state) => {
|
||||
if (state === SignInMethodState.primary) {
|
||||
return;
|
||||
}
|
||||
const signInExperience = {
|
||||
signInMethods: {
|
||||
username: SignInMethodState.disabled,
|
||||
email: SignInMethodState.disabled,
|
||||
sms: state,
|
||||
social: SignInMethodState.primary,
|
||||
},
|
||||
socialSignInConnectorIds: ['github'],
|
||||
};
|
||||
await expectPatchResponseStatus(signInExperience, 200);
|
||||
});
|
||||
|
||||
test.each(invalidSignInMethodStates)('%p should fail', async (state) => {
|
||||
if (state === SignInMethodState.primary) {
|
||||
return;
|
||||
}
|
||||
const signInExperience = {
|
||||
signInMethods: {
|
||||
username: SignInMethodState.disabled,
|
||||
email: SignInMethodState.disabled,
|
||||
sms: state,
|
||||
social: SignInMethodState.primary,
|
||||
},
|
||||
socialSignInConnectorIds: ['github'],
|
||||
};
|
||||
await expectPatchResponseStatus(signInExperience, 400);
|
||||
});
|
||||
});
|
||||
|
||||
describe('social', () => {
|
||||
test.each(validSignInMethodStates)('%p should success', async (state) => {
|
||||
if (state === SignInMethodState.primary) {
|
||||
return;
|
||||
}
|
||||
const signInExperience = {
|
||||
signInMethods: {
|
||||
username: SignInMethodState.primary,
|
||||
email: SignInMethodState.disabled,
|
||||
sms: SignInMethodState.disabled,
|
||||
social: state,
|
||||
},
|
||||
socialSignInConnectorIds: ['github'],
|
||||
};
|
||||
await expectPatchResponseStatus(signInExperience, 200);
|
||||
});
|
||||
|
||||
test.each(invalidSignInMethodStates)('%p should fail', async (state) => {
|
||||
if (state === SignInMethodState.primary) {
|
||||
return;
|
||||
}
|
||||
const signInExperience = {
|
||||
signInMethods: {
|
||||
username: SignInMethodState.primary,
|
||||
email: SignInMethodState.disabled,
|
||||
sms: SignInMethodState.disabled,
|
||||
social: state,
|
||||
},
|
||||
socialSignInConnectorIds: ['github'],
|
||||
};
|
||||
await expectPatchResponseStatus(signInExperience, 400);
|
||||
});
|
||||
expect(response.status).toEqual(400);
|
||||
});
|
||||
});
|
||||
|
||||
describe('socialSignInConnectorIds', () => {
|
||||
test.each([[['facebook']], [['facebook', 'github']]])(
|
||||
'%p should success',
|
||||
async (socialSignInConnectorIds) => {
|
||||
await expectPatchResponseStatus(
|
||||
{
|
||||
signInMethods: { ...mockSignInMethods, social: SignInMethodState.secondary },
|
||||
socialSignInConnectorIds,
|
||||
},
|
||||
200
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
test.each([[[]], [[null, undefined]], [['', ' \t\n\r']], [[123, 456]]])(
|
||||
'%p should fail',
|
||||
async (socialSignInConnectorIds: any[]) => {
|
||||
await expectPatchResponseStatus(
|
||||
{
|
||||
signInMethods: { ...mockSignInMethods, social: SignInMethodState.secondary },
|
||||
socialSignInConnectorIds,
|
||||
},
|
||||
400
|
||||
);
|
||||
}
|
||||
);
|
||||
});
|
||||
|
|
|
@ -430,6 +430,18 @@ export const mockAliyunDmConnectorInstance = {
|
|||
},
|
||||
};
|
||||
|
||||
export const mockAliyunSmsConnectorInstance = {
|
||||
connector: {
|
||||
id: 'aliyun-sms',
|
||||
enabled: true,
|
||||
config: {},
|
||||
createdAt: 1_646_382_233_333,
|
||||
},
|
||||
metadata: {
|
||||
type: ConnectorType.SMS,
|
||||
},
|
||||
};
|
||||
|
||||
export const mockFacebookConnectorInstance = {
|
||||
connector: {
|
||||
id: 'facebook',
|
||||
|
|
Loading…
Add table
Reference in a new issue