mirror of
https://github.com/logto-io/logto.git
synced 2025-03-31 22:51:25 -05:00
refactor(core): migrate sie library to factory mode
This commit is contained in:
parent
8561b6bc43
commit
ecbf028f4e
16 changed files with 262 additions and 180 deletions
5
packages/core/src/libraries/shared.ts
Normal file
5
packages/core/src/libraries/shared.ts
Normal file
|
@ -0,0 +1,5 @@
|
|||
import envSet from '#src/env-set/index.js';
|
||||
import Queries from '#src/tenants/Queries.js';
|
||||
|
||||
/** @deprecated Don't use. This is for transition only and will be removed soon. */
|
||||
export const defaultQueries = new Queries(envSet.pool);
|
|
@ -18,34 +18,42 @@ const { mockEsm } = createMockUtils(jest);
|
|||
|
||||
const allCustomLanguageTags: LanguageTag[] = [];
|
||||
|
||||
const { findAllCustomLanguageTags } = mockEsm('#src/queries/custom-phrase.js', () => ({
|
||||
const customPhrases = {
|
||||
findAllCustomLanguageTags: jest.fn(async () => allCustomLanguageTags),
|
||||
}));
|
||||
};
|
||||
const { findAllCustomLanguageTags } = customPhrases;
|
||||
|
||||
const { getLogtoConnectors } = mockEsm('#src/connectors.js', () => ({
|
||||
getLogtoConnectors: jest.fn(),
|
||||
}));
|
||||
const { findDefaultSignInExperience, updateDefaultSignInExperience } = mockEsm(
|
||||
'#src/queries/sign-in-experience.js',
|
||||
() => ({
|
||||
findDefaultSignInExperience: jest.fn(),
|
||||
updateDefaultSignInExperience: jest.fn(
|
||||
async (data: Partial<CreateSignInExperience>): Promise<SignInExperience> => ({
|
||||
...mockSignInExperience,
|
||||
...data,
|
||||
})
|
||||
),
|
||||
})
|
||||
);
|
||||
|
||||
const { validateBranding, validateLanguageInfo, removeUnavailableSocialConnectorTargets } =
|
||||
await import('./index.js');
|
||||
const signInExperiences = {
|
||||
findDefaultSignInExperience: jest.fn(),
|
||||
updateDefaultSignInExperience: jest.fn(
|
||||
async (data: Partial<CreateSignInExperience>): Promise<SignInExperience> => ({
|
||||
...mockSignInExperience,
|
||||
...data,
|
||||
})
|
||||
),
|
||||
};
|
||||
const { findDefaultSignInExperience, updateDefaultSignInExperience } = signInExperiences;
|
||||
|
||||
const { MockQueries } = await import('#src/test-utils/tenant.js');
|
||||
const queries = new MockQueries({
|
||||
customPhrases,
|
||||
signInExperiences,
|
||||
});
|
||||
|
||||
const { validateBranding, createSignInExperienceLibrary } = await import('./index.js');
|
||||
const { validateLanguageInfo, removeUnavailableSocialConnectorTargets } =
|
||||
createSignInExperienceLibrary(queries);
|
||||
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
|
||||
describe('validate branding', () => {
|
||||
test('should throw when the UI style contains the slogan and slogan is empty', () => {
|
||||
it('should throw when the UI style contains the slogan and slogan is empty', () => {
|
||||
expect(() => {
|
||||
validateBranding({
|
||||
...mockBranding,
|
||||
|
@ -55,7 +63,7 @@ describe('validate branding', () => {
|
|||
}).toMatchError(new RequestError('sign_in_experiences.empty_slogan'));
|
||||
});
|
||||
|
||||
test('should throw when the logo is empty', () => {
|
||||
it('should throw when the logo is empty', () => {
|
||||
expect(() => {
|
||||
validateBranding({
|
||||
...mockBranding,
|
||||
|
@ -66,7 +74,7 @@ describe('validate branding', () => {
|
|||
}).toMatchError(new RequestError('sign_in_experiences.empty_logo'));
|
||||
});
|
||||
|
||||
test('should throw when the UI style contains the slogan and slogan is blank', () => {
|
||||
it('should throw when the UI style contains the slogan and slogan is blank', () => {
|
||||
expect(() => {
|
||||
validateBranding({
|
||||
...mockBranding,
|
||||
|
@ -76,7 +84,7 @@ describe('validate branding', () => {
|
|||
}).toMatchError(new RequestError('sign_in_experiences.empty_slogan'));
|
||||
});
|
||||
|
||||
test('should not throw when the UI style does not contain the slogan and slogan is empty', () => {
|
||||
it('should not throw when the UI style does not contain the slogan and slogan is empty', () => {
|
||||
expect(() => {
|
||||
validateBranding({
|
||||
...mockBranding,
|
||||
|
@ -138,7 +146,7 @@ describe('validate language info', () => {
|
|||
});
|
||||
|
||||
describe('remove unavailable social connector targets', () => {
|
||||
test('should remove unavailable social connector targets in sign-in experience', async () => {
|
||||
it('should remove unavailable social connector targets in sign-in experience', async () => {
|
||||
const mockSocialConnectorTargets = mockSocialConnectors.map(
|
||||
({ metadata: { target } }) => target
|
||||
);
|
||||
|
|
|
@ -13,14 +13,11 @@ import i18next from 'i18next';
|
|||
|
||||
import { getLogtoConnectors } from '#src/connectors/index.js';
|
||||
import RequestError from '#src/errors/RequestError/index.js';
|
||||
import { findAllCustomLanguageTags } from '#src/queries/custom-phrase.js';
|
||||
import {
|
||||
findDefaultSignInExperience,
|
||||
updateDefaultSignInExperience,
|
||||
} from '#src/queries/sign-in-experience.js';
|
||||
import { hasActiveUsers } from '#src/queries/user.js';
|
||||
import type Queries from '#src/tenants/Queries.js';
|
||||
import assertThat from '#src/utils/assert-that.js';
|
||||
|
||||
import { defaultQueries } from '../shared.js';
|
||||
|
||||
export * from './sign-up.js';
|
||||
export * from './sign-in.js';
|
||||
|
||||
|
@ -32,66 +29,88 @@ export const validateBranding = (branding: Branding) => {
|
|||
assertThat(branding.logoUrl.trim(), 'sign_in_experiences.empty_logo');
|
||||
};
|
||||
|
||||
export const validateLanguageInfo = async (languageInfo: LanguageInfo) => {
|
||||
const supportedLanguages = [...builtInLanguages, ...(await findAllCustomLanguageTags())];
|
||||
export const createSignInExperienceLibrary = (queries: Queries) => {
|
||||
const {
|
||||
customPhrases: { findAllCustomLanguageTags },
|
||||
signInExperiences: { findDefaultSignInExperience, updateDefaultSignInExperience },
|
||||
users: { hasActiveUsers },
|
||||
} = queries;
|
||||
|
||||
assertThat(
|
||||
supportedLanguages.includes(languageInfo.fallbackLanguage),
|
||||
new RequestError({
|
||||
code: 'sign_in_experiences.unsupported_default_language',
|
||||
language: languageInfo.fallbackLanguage,
|
||||
})
|
||||
);
|
||||
const validateLanguageInfo = async (languageInfo: LanguageInfo) => {
|
||||
const supportedLanguages = [...builtInLanguages, ...(await findAllCustomLanguageTags())];
|
||||
|
||||
assertThat(
|
||||
supportedLanguages.includes(languageInfo.fallbackLanguage),
|
||||
new RequestError({
|
||||
code: 'sign_in_experiences.unsupported_default_language',
|
||||
language: languageInfo.fallbackLanguage,
|
||||
})
|
||||
);
|
||||
};
|
||||
|
||||
const removeUnavailableSocialConnectorTargets = async () => {
|
||||
const connectors = await getLogtoConnectors();
|
||||
const availableSocialConnectorTargets = deduplicate(
|
||||
connectors
|
||||
.filter(({ type }) => type === ConnectorType.Social)
|
||||
.map(({ metadata: { target } }) => target)
|
||||
);
|
||||
|
||||
const { socialSignInConnectorTargets } = await findDefaultSignInExperience();
|
||||
|
||||
await updateDefaultSignInExperience({
|
||||
socialSignInConnectorTargets: socialSignInConnectorTargets.filter((target) =>
|
||||
availableSocialConnectorTargets.includes(target)
|
||||
),
|
||||
});
|
||||
};
|
||||
|
||||
const getSignInExperienceForApplication = async (
|
||||
applicationId?: string
|
||||
): Promise<SignInExperience & { notification?: string }> => {
|
||||
const signInExperience = await findDefaultSignInExperience();
|
||||
|
||||
// Hard code AdminConsole sign-in methods settings.
|
||||
if (applicationId === adminConsoleApplicationId) {
|
||||
return {
|
||||
...adminConsoleSignInExperience,
|
||||
branding: {
|
||||
...adminConsoleSignInExperience.branding,
|
||||
slogan: i18next.t('admin_console.welcome.title'),
|
||||
},
|
||||
termsOfUseUrl: signInExperience.termsOfUseUrl,
|
||||
languageInfo: signInExperience.languageInfo,
|
||||
signInMode: (await hasActiveUsers()) ? SignInMode.SignIn : SignInMode.Register,
|
||||
socialSignInConnectorTargets: [],
|
||||
};
|
||||
}
|
||||
|
||||
// Insert Demo App Notification
|
||||
if (applicationId === demoAppApplicationId) {
|
||||
const { socialSignInConnectorTargets } = signInExperience;
|
||||
|
||||
const notification = i18next.t('demo_app.notification');
|
||||
|
||||
return {
|
||||
...signInExperience,
|
||||
socialSignInConnectorTargets,
|
||||
notification,
|
||||
};
|
||||
}
|
||||
|
||||
return signInExperience;
|
||||
};
|
||||
|
||||
return {
|
||||
validateLanguageInfo,
|
||||
removeUnavailableSocialConnectorTargets,
|
||||
getSignInExperienceForApplication,
|
||||
};
|
||||
};
|
||||
|
||||
export const removeUnavailableSocialConnectorTargets = async () => {
|
||||
const connectors = await getLogtoConnectors();
|
||||
const availableSocialConnectorTargets = deduplicate(
|
||||
connectors
|
||||
.filter(({ type }) => type === ConnectorType.Social)
|
||||
.map(({ metadata: { target } }) => target)
|
||||
);
|
||||
|
||||
const { socialSignInConnectorTargets } = await findDefaultSignInExperience();
|
||||
await updateDefaultSignInExperience({
|
||||
socialSignInConnectorTargets: socialSignInConnectorTargets.filter((target) =>
|
||||
availableSocialConnectorTargets.includes(target)
|
||||
),
|
||||
});
|
||||
};
|
||||
|
||||
export const getSignInExperienceForApplication = async (
|
||||
applicationId?: string
|
||||
): Promise<SignInExperience & { notification?: string }> => {
|
||||
const signInExperience = await findDefaultSignInExperience();
|
||||
|
||||
// Hard code AdminConsole sign-in methods settings.
|
||||
if (applicationId === adminConsoleApplicationId) {
|
||||
return {
|
||||
...adminConsoleSignInExperience,
|
||||
branding: {
|
||||
...adminConsoleSignInExperience.branding,
|
||||
slogan: i18next.t('admin_console.welcome.title'),
|
||||
},
|
||||
termsOfUseUrl: signInExperience.termsOfUseUrl,
|
||||
languageInfo: signInExperience.languageInfo,
|
||||
signInMode: (await hasActiveUsers()) ? SignInMode.SignIn : SignInMode.Register,
|
||||
socialSignInConnectorTargets: [],
|
||||
};
|
||||
}
|
||||
|
||||
// Insert Demo App Notification
|
||||
if (applicationId === demoAppApplicationId) {
|
||||
const { socialSignInConnectorTargets } = signInExperience;
|
||||
|
||||
const notification = i18next.t('demo_app.notification');
|
||||
|
||||
return {
|
||||
...signInExperience,
|
||||
socialSignInConnectorTargets,
|
||||
notification,
|
||||
};
|
||||
}
|
||||
|
||||
return signInExperience;
|
||||
};
|
||||
/** @deprecated Don't use. This is for transition only and will be removed soon. */
|
||||
export const {
|
||||
validateLanguageInfo,
|
||||
removeUnavailableSocialConnectorTargets,
|
||||
getSignInExperienceForApplication,
|
||||
} = createSignInExperienceLibrary(defaultQueries);
|
||||
|
|
|
@ -10,16 +10,15 @@ import pRetry from 'p-retry';
|
|||
import { buildInsertIntoWithPool } from '#src/database/insert-into.js';
|
||||
import envSet from '#src/env-set/index.js';
|
||||
import RequestError from '#src/errors/RequestError/index.js';
|
||||
import Queries from '#src/tenants/Queries.js';
|
||||
import type Queries from '#src/tenants/Queries.js';
|
||||
import assertThat from '#src/utils/assert-that.js';
|
||||
import { encryptPassword } from '#src/utils/password.js';
|
||||
|
||||
import { defaultQueries } from './shared.js';
|
||||
|
||||
const userId = buildIdGenerator(12);
|
||||
const roleId = buildIdGenerator(21);
|
||||
|
||||
/** @deprecated Don't use. This is for transition only and will be removed soon. */
|
||||
export const defaultQueries = new Queries(envSet.pool);
|
||||
|
||||
export const encryptUserPassword = async (
|
||||
password: string
|
||||
): Promise<{
|
||||
|
|
|
@ -30,7 +30,7 @@ const { findApplicationById } = await mockEsmWithActual('#src/queries/applicatio
|
|||
),
|
||||
}));
|
||||
|
||||
mockEsm('@logto/core-kit', () => ({
|
||||
await mockEsmWithActual('@logto/core-kit', () => ({
|
||||
// eslint-disable-next-line unicorn/consistent-function-scoping
|
||||
buildIdGenerator: () => () => 'randomId',
|
||||
generateStandardId: () => 'randomId',
|
||||
|
|
|
@ -23,7 +23,7 @@ import resourceRoutes from './resource.js';
|
|||
import roleRoutes from './role.js';
|
||||
import sessionRoutes from './session/index.js';
|
||||
import settingRoutes from './setting.js';
|
||||
import signInExperiencesRoutes from './sign-in-experience.js';
|
||||
import signInExperiencesRoutes from './sign-in-experience/index.js';
|
||||
import statusRoutes from './status.js';
|
||||
import swaggerRoutes from './swagger.js';
|
||||
import type { AnonymousRouter, AnonymousRouterLegacy, AuthedRouter } from './types.js';
|
||||
|
|
|
@ -73,14 +73,11 @@ export default function profileRoutes<T extends AnonymousRouter>(
|
|||
body: object({ username: string().regex(usernameRegEx) }),
|
||||
}),
|
||||
async (ctx, next) => {
|
||||
console.log('?0');
|
||||
const userId = await checkSessionHealth(ctx, tenant, verificationTimeout);
|
||||
assertThat(userId, new RequestError({ code: 'auth.unauthorized', status: 401 }));
|
||||
|
||||
const { username } = ctx.guard.body;
|
||||
console.log('?1');
|
||||
await checkIdentifierCollision({ username }, userId);
|
||||
console.log('?2');
|
||||
await updateUserById(userId, { username }, 'replace');
|
||||
|
||||
ctx.status = 204;
|
||||
|
|
|
@ -3,25 +3,30 @@ import { BrandingStyle } from '@logto/schemas';
|
|||
import { pickDefault, createMockUtils } from '@logto/shared/esm';
|
||||
|
||||
import { mockBranding, mockSignInExperience } from '#src/__mocks__/index.js';
|
||||
import { MockTenant } from '#src/test-utils/tenant.js';
|
||||
import { createRequester } from '#src/utils/test-utils.js';
|
||||
|
||||
const { mockEsm, mockEsmWithActual } = createMockUtils(import.meta.jest);
|
||||
|
||||
await mockEsmWithActual('#src/queries/sign-in-experience.js', () => ({
|
||||
updateDefaultSignInExperience: async (
|
||||
data: Partial<CreateSignInExperience>
|
||||
): Promise<SignInExperience> => ({
|
||||
...mockSignInExperience,
|
||||
...data,
|
||||
}),
|
||||
}));
|
||||
const { mockEsm } = createMockUtils(import.meta.jest);
|
||||
|
||||
mockEsm('#src/connectors.js', () => ({
|
||||
getLogtoConnectors: async () => [],
|
||||
}));
|
||||
|
||||
const signInExperiencesRoutes = await pickDefault(import('./sign-in-experience.js'));
|
||||
const signInExperienceRequester = createRequester({ authedRoutes: signInExperiencesRoutes });
|
||||
const tenantContext = new MockTenant(undefined, {
|
||||
signInExperiences: {
|
||||
updateDefaultSignInExperience: async (
|
||||
data: Partial<CreateSignInExperience>
|
||||
): Promise<SignInExperience> => ({
|
||||
...mockSignInExperience,
|
||||
...data,
|
||||
}),
|
||||
},
|
||||
});
|
||||
const signInExperiencesRoutes = await pickDefault(import('./index.js'));
|
||||
const signInExperienceRequester = createRequester({
|
||||
authedRoutes: signInExperiencesRoutes,
|
||||
tenantContext,
|
||||
});
|
||||
|
||||
const expectPatchResponseStatus = async (
|
||||
signInExperience: Record<string, unknown>,
|
|
@ -2,25 +2,29 @@ import type { CreateSignInExperience, SignInExperience } from '@logto/schemas';
|
|||
import { pickDefault, createMockUtils } from '@logto/shared/esm';
|
||||
|
||||
import { mockColor, mockSignInExperience } from '#src/__mocks__/index.js';
|
||||
import { MockTenant } from '#src/test-utils/tenant.js';
|
||||
import { createRequester } from '#src/utils/test-utils.js';
|
||||
|
||||
const { mockEsm, mockEsmWithActual } = createMockUtils(import.meta.jest);
|
||||
|
||||
await mockEsmWithActual('#src/queries/sign-in-experience.js', () => ({
|
||||
updateDefaultSignInExperience: async (
|
||||
data: Partial<CreateSignInExperience>
|
||||
): Promise<SignInExperience> => ({
|
||||
...mockSignInExperience,
|
||||
...data,
|
||||
}),
|
||||
}));
|
||||
const { mockEsm } = createMockUtils(import.meta.jest);
|
||||
|
||||
mockEsm('#src/connectors.js', () => ({
|
||||
getLogtoConnectors: async () => [],
|
||||
}));
|
||||
|
||||
const signInExperiencesRoutes = await pickDefault(import('./sign-in-experience.js'));
|
||||
const signInExperienceRequester = createRequester({ authedRoutes: signInExperiencesRoutes });
|
||||
const signInExperiencesRoutes = await pickDefault(import('./index.js'));
|
||||
const signInExperienceRequester = createRequester({
|
||||
authedRoutes: signInExperiencesRoutes,
|
||||
tenantContext: new MockTenant(undefined, {
|
||||
signInExperiences: {
|
||||
updateDefaultSignInExperience: async (
|
||||
data: Partial<CreateSignInExperience>
|
||||
): Promise<SignInExperience> => ({
|
||||
...mockSignInExperience,
|
||||
...data,
|
||||
}),
|
||||
},
|
||||
}),
|
||||
});
|
||||
|
||||
const expectPatchResponseStatus = async (
|
||||
signInExperience: Record<string, unknown>,
|
|
@ -10,9 +10,10 @@ import {
|
|||
mockLanguageInfo,
|
||||
mockSignInExperience,
|
||||
} from '#src/__mocks__/index.js';
|
||||
import { MockTenant } from '#src/test-utils/tenant.js';
|
||||
|
||||
const { jest } = import.meta;
|
||||
const { mockEsm, mockEsmWithActual } = createMockUtils(jest);
|
||||
const { mockEsm } = createMockUtils(jest);
|
||||
|
||||
mockEsm('#src/connectors.js', () => ({
|
||||
getLogtoConnectors: jest.fn(async () => [
|
||||
|
@ -24,25 +25,33 @@ mockEsm('#src/connectors.js', () => ({
|
|||
]),
|
||||
}));
|
||||
|
||||
const { validateLanguageInfo } = await mockEsmWithActual(
|
||||
'#src/libraries/sign-in-experience.js',
|
||||
() => ({
|
||||
validateLanguageInfo: jest.fn(),
|
||||
})
|
||||
const validateLanguageInfo = jest.fn();
|
||||
|
||||
const tenantContext = new MockTenant(
|
||||
undefined,
|
||||
{
|
||||
signInExperiences: {
|
||||
updateDefaultSignInExperience: async (
|
||||
data: Partial<CreateSignInExperience>
|
||||
): Promise<SignInExperience> => ({
|
||||
...mockSignInExperience,
|
||||
...data,
|
||||
}),
|
||||
},
|
||||
},
|
||||
{
|
||||
signInExperiences: {
|
||||
validateLanguageInfo,
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
await mockEsmWithActual('#src/queries/sign-in-experience.js', () => ({
|
||||
updateDefaultSignInExperience: async (
|
||||
data: Partial<CreateSignInExperience>
|
||||
): Promise<SignInExperience> => ({
|
||||
...mockSignInExperience,
|
||||
...data,
|
||||
}),
|
||||
}));
|
||||
|
||||
const signInExperiencesRoutes = await pickDefault(import('./sign-in-experience.js'));
|
||||
const signInExperiencesRoutes = await pickDefault(import('./index.js'));
|
||||
const { createRequester } = await import('#src/utils/test-utils.js');
|
||||
const signInExperienceRequester = createRequester({ authedRoutes: signInExperiencesRoutes });
|
||||
const signInExperienceRequester = createRequester({
|
||||
authedRoutes: signInExperiencesRoutes,
|
||||
tenantContext,
|
||||
});
|
||||
|
||||
const expectPatchResponseStatus = async (
|
||||
signInExperience: Record<string, unknown>,
|
|
@ -15,18 +15,11 @@ import {
|
|||
mockAliyunSmsConnector,
|
||||
mockTermsOfUseUrl,
|
||||
} from '#src/__mocks__/index.js';
|
||||
import { MockTenant } from '#src/test-utils/tenant.js';
|
||||
import { createRequester } from '#src/utils/test-utils.js';
|
||||
|
||||
const { jest } = import.meta;
|
||||
const { mockEsm, mockEsmWithActual } = createMockUtils(jest);
|
||||
|
||||
const { validateBranding, validateLanguageInfo, validateSignIn, validateSignUp } =
|
||||
await mockEsmWithActual('#src/libraries/sign-in-experience/index.js', () => ({
|
||||
validateBranding: jest.fn(),
|
||||
validateLanguageInfo: jest.fn(),
|
||||
validateSignIn: jest.fn(),
|
||||
validateSignUp: jest.fn(),
|
||||
}));
|
||||
const { mockEsmWithActual } = createMockUtils(jest);
|
||||
|
||||
const logtoConnectors = [
|
||||
mockFacebookConnector,
|
||||
|
@ -40,7 +33,16 @@ await mockEsmWithActual('#src/connectors.js', () => ({
|
|||
getLogtoConnectors: async () => logtoConnectors,
|
||||
}));
|
||||
|
||||
const { findDefaultSignInExperience } = mockEsm('#src/queries/sign-in-experience.js', () => ({
|
||||
const { validateBranding, validateSignIn, validateSignUp } = await mockEsmWithActual(
|
||||
'#src/libraries/sign-in-experience/index.js',
|
||||
() => ({
|
||||
validateBranding: jest.fn(),
|
||||
validateSignIn: jest.fn(),
|
||||
validateSignUp: jest.fn(),
|
||||
})
|
||||
);
|
||||
|
||||
const signInExperiences = {
|
||||
findDefaultSignInExperience: jest.fn(async () => mockSignInExperience),
|
||||
updateDefaultSignInExperience: async (
|
||||
data: Partial<CreateSignInExperience>
|
||||
|
@ -48,14 +50,22 @@ const { findDefaultSignInExperience } = mockEsm('#src/queries/sign-in-experience
|
|||
...mockSignInExperience,
|
||||
...data,
|
||||
}),
|
||||
}));
|
||||
};
|
||||
const { findDefaultSignInExperience } = signInExperiences;
|
||||
|
||||
mockEsm('#src/queries/custom-phrase.js', () => ({
|
||||
findAllCustomLanguageTags: async () => [],
|
||||
}));
|
||||
const validateLanguageInfo = jest.fn();
|
||||
|
||||
const signInExperiencesRoutes = await pickDefault(import('./sign-in-experience.js'));
|
||||
const signInExperienceRequester = createRequester({ authedRoutes: signInExperiencesRoutes });
|
||||
const tenantContext = new MockTenant(
|
||||
undefined,
|
||||
{ signInExperiences, customPhrases: { findAllCustomLanguageTags: async () => [] } },
|
||||
{ signInExperiences: { validateLanguageInfo } }
|
||||
);
|
||||
|
||||
const signInExperiencesRoutes = await pickDefault(import('./index.js'));
|
||||
const signInExperienceRequester = createRequester({
|
||||
authedRoutes: signInExperiencesRoutes,
|
||||
tenantContext,
|
||||
});
|
||||
|
||||
describe('GET /sign-in-exp', () => {
|
||||
afterAll(() => {
|
|
@ -4,21 +4,19 @@ import { literal, object, string } from 'zod';
|
|||
import { getLogtoConnectors } from '#src/connectors/index.js';
|
||||
import {
|
||||
validateBranding,
|
||||
validateLanguageInfo,
|
||||
validateSignUp,
|
||||
validateSignIn,
|
||||
} from '#src/libraries/sign-in-experience/index.js';
|
||||
import koaGuard from '#src/middleware/koa-guard.js';
|
||||
import {
|
||||
findDefaultSignInExperience,
|
||||
updateDefaultSignInExperience,
|
||||
} from '#src/queries/sign-in-experience.js';
|
||||
|
||||
import type { AuthedRouter, RouterInitArgs } from './types.js';
|
||||
import type { AuthedRouter, RouterInitArgs } from '../types.js';
|
||||
|
||||
export default function signInExperiencesRoutes<T extends AuthedRouter>(
|
||||
...[router]: RouterInitArgs<T>
|
||||
...[router, { queries, libraries }]: RouterInitArgs<T>
|
||||
) {
|
||||
const { findDefaultSignInExperience, updateDefaultSignInExperience } = queries.signInExperiences;
|
||||
const { validateLanguageInfo } = libraries.signInExperiences;
|
||||
|
||||
/**
|
||||
* As we only support single signInExperience settings for V1
|
||||
* always return the default settings in DB for the /sign-in-exp get method
|
|
@ -15,9 +15,6 @@ import {
|
|||
mockWechatConnector,
|
||||
mockWechatNativeConnector,
|
||||
} from '#src/__mocks__/index.js';
|
||||
import { createMockProvider } from '#src/test-utils/oidc-provider.js';
|
||||
import { MockTenant } from '#src/test-utils/tenant.js';
|
||||
import { createRequester } from '#src/utils/test-utils.js';
|
||||
|
||||
const { jest } = import.meta;
|
||||
const { mockEsm, mockEsmWithActual } = createMockUtils(jest);
|
||||
|
@ -28,14 +25,11 @@ await mockEsmWithActual('i18next', () => ({
|
|||
},
|
||||
}));
|
||||
|
||||
const { findDefaultSignInExperience } = mockEsm('#src/queries/sign-in-experience.js', () => ({
|
||||
const sieQueries = {
|
||||
updateDefaultSignInExperience: jest.fn(),
|
||||
findDefaultSignInExperience: jest.fn().mockResolvedValue(mockSignInExperience),
|
||||
}));
|
||||
|
||||
await mockEsmWithActual('#src/queries/user.js', () => ({
|
||||
hasActiveUsers: jest.fn().mockResolvedValue(true),
|
||||
}));
|
||||
};
|
||||
const { findDefaultSignInExperience } = sieQueries;
|
||||
|
||||
mockEsm('#src/connectors.js', () => ({
|
||||
getLogtoConnectors: jest.fn(async () => [
|
||||
|
@ -50,6 +44,9 @@ mockEsm('#src/connectors.js', () => ({
|
|||
}));
|
||||
|
||||
const wellKnownRoutes = await pickDefault(import('#src/routes/well-known.js'));
|
||||
const { createMockProvider } = await import('#src/test-utils/oidc-provider.js');
|
||||
const { MockTenant } = await import('#src/test-utils/tenant.js');
|
||||
const { createRequester } = await import('#src/utils/test-utils.js');
|
||||
|
||||
describe('GET /.well-known/sign-in-exp', () => {
|
||||
afterEach(() => {
|
||||
|
@ -59,7 +56,10 @@ describe('GET /.well-known/sign-in-exp', () => {
|
|||
const provider = createMockProvider();
|
||||
const sessionRequest = createRequester({
|
||||
anonymousRoutes: wellKnownRoutes,
|
||||
tenantContext: new MockTenant(provider),
|
||||
tenantContext: new MockTenant(provider, {
|
||||
signInExperiences: sieQueries,
|
||||
users: { hasActiveUsers: jest.fn().mockResolvedValue(true) },
|
||||
}),
|
||||
middlewares: [
|
||||
async (ctx, next) => {
|
||||
ctx.addLogContext = jest.fn();
|
||||
|
|
|
@ -5,13 +5,14 @@ import etag from 'etag';
|
|||
|
||||
import { getLogtoConnectors } from '#src/connectors/index.js';
|
||||
import { getApplicationIdFromInteraction } from '#src/libraries/session.js';
|
||||
import { getSignInExperienceForApplication } from '#src/libraries/sign-in-experience/index.js';
|
||||
|
||||
import type { AnonymousRouter, RouterInitArgs } from './types.js';
|
||||
|
||||
export default function wellKnownRoutes<T extends AnonymousRouter>(
|
||||
...[router, { provider }]: RouterInitArgs<T>
|
||||
...[router, { provider, libraries }]: RouterInitArgs<T>
|
||||
) {
|
||||
const { getSignInExperienceForApplication } = libraries.signInExperiences;
|
||||
|
||||
router.get(
|
||||
'/.well-known/sign-in-exp',
|
||||
async (ctx, next) => {
|
||||
|
|
|
@ -1,9 +1,11 @@
|
|||
import { createSignInExperienceLibrary } from '#src/libraries/sign-in-experience/index.js';
|
||||
import { createUserLibrary } from '#src/libraries/user.js';
|
||||
|
||||
import type Queries from './Queries.js';
|
||||
|
||||
export default class Libraries {
|
||||
users = createUserLibrary(this.queries);
|
||||
signInExperiences = createSignInExperienceLibrary(this.queries);
|
||||
|
||||
constructor(public readonly queries: Queries) {}
|
||||
}
|
||||
|
|
|
@ -6,13 +6,39 @@ import type TenantContext from '#src/tenants/TenantContext.js';
|
|||
|
||||
import { createMockProvider } from './oidc-provider.js';
|
||||
|
||||
const { jest } = import.meta;
|
||||
export const createQueriesWithMockPool = () =>
|
||||
new Queries(
|
||||
createMockPool({
|
||||
query: async (sql, values) => {
|
||||
return createMockQueryResult([]);
|
||||
},
|
||||
})
|
||||
);
|
||||
|
||||
const pool = createMockPool({
|
||||
query: async (sql, values) => {
|
||||
return createMockQueryResult([]);
|
||||
},
|
||||
});
|
||||
export class MockQueries extends Queries {
|
||||
constructor(queriesOverride?: Partial2<Queries>) {
|
||||
super(
|
||||
createMockPool({
|
||||
query: async (sql, values) => {
|
||||
return createMockQueryResult([]);
|
||||
},
|
||||
})
|
||||
);
|
||||
|
||||
if (!queriesOverride) {
|
||||
return;
|
||||
}
|
||||
|
||||
const overrideKey = <Key extends keyof Queries>(key: Key) => {
|
||||
this[key] = { ...this[key], ...queriesOverride[key] };
|
||||
};
|
||||
|
||||
// eslint-disable-next-line no-restricted-syntax
|
||||
for (const key of Object.keys(queriesOverride) as Array<keyof Queries>) {
|
||||
overrideKey(key);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/ban-types
|
||||
export type DeepPartial<T> = T extends object
|
||||
|
@ -32,8 +58,7 @@ export class MockTenant implements TenantContext {
|
|||
queriesOverride?: Partial2<Queries>,
|
||||
librariesOverride?: Partial2<Libraries>
|
||||
) {
|
||||
this.queries = new Queries(pool);
|
||||
this.setPartial('queries', queriesOverride);
|
||||
this.queries = new MockQueries(queriesOverride);
|
||||
this.libraries = new Libraries(this.queries);
|
||||
this.setPartial('libraries', librariesOverride);
|
||||
}
|
||||
|
|
Loading…
Add table
Reference in a new issue