mirror of
https://github.com/logto-io/logto.git
synced 2024-12-16 20:26:19 -05:00
chore: add integration tests
This commit is contained in:
parent
92625e5019
commit
9c59102f97
4 changed files with 131 additions and 4 deletions
|
@ -7,12 +7,17 @@ import type {
|
||||||
|
|
||||||
import { authedAdminApi } from './api.js';
|
import { authedAdminApi } from './api.js';
|
||||||
|
|
||||||
export const createApplication = async (name: string, type: ApplicationType) =>
|
export const createApplication = async (
|
||||||
|
name: string,
|
||||||
|
type: ApplicationType,
|
||||||
|
rest?: Partial<CreateApplication>
|
||||||
|
) =>
|
||||||
authedAdminApi
|
authedAdminApi
|
||||||
.post('applications', {
|
.post('applications', {
|
||||||
json: {
|
json: {
|
||||||
name,
|
name,
|
||||||
type,
|
type,
|
||||||
|
...rest,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
.json<Application>();
|
.json<Application>();
|
||||||
|
|
|
@ -0,0 +1,68 @@
|
||||||
|
import { Prompt } from '@logto/js';
|
||||||
|
import { ApplicationType, InteractionEvent } from '@logto/schemas';
|
||||||
|
|
||||||
|
import { createApplication, deleteApplication, putInteraction } from '#src/api/index.js';
|
||||||
|
import MockClient from '#src/client/index.js';
|
||||||
|
import { demoAppRedirectUri } from '#src/constants.js';
|
||||||
|
import { processSession } from '#src/helpers/client.js';
|
||||||
|
import { createUserByAdmin } from '#src/helpers/index.js';
|
||||||
|
import { enableAllPasswordSignInMethods } from '#src/helpers/sign-in-experience.js';
|
||||||
|
import { generateUsername, generatePassword } from '#src/utils.js';
|
||||||
|
|
||||||
|
describe('always issue Refresh Token config', () => {
|
||||||
|
const username = generateUsername();
|
||||||
|
const password = generatePassword();
|
||||||
|
|
||||||
|
const validateRefreshToken = async (appId: string, redirectUri: string, expectToken: boolean) => {
|
||||||
|
const client = new MockClient({
|
||||||
|
appId,
|
||||||
|
prompt: Prompt.Login,
|
||||||
|
});
|
||||||
|
await client.initSession(redirectUri);
|
||||||
|
await client.successSend(putInteraction, {
|
||||||
|
event: InteractionEvent.SignIn,
|
||||||
|
identifier: { username, password },
|
||||||
|
});
|
||||||
|
const { redirectTo } = await client.submitInteraction();
|
||||||
|
await processSession(client, redirectTo);
|
||||||
|
|
||||||
|
if (expectToken) {
|
||||||
|
expect(await client.getRefreshToken()).not.toBeNull();
|
||||||
|
} else {
|
||||||
|
expect(await client.getRefreshToken()).toBeNull();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
beforeAll(async () => {
|
||||||
|
await createUserByAdmin(username, password);
|
||||||
|
await enableAllPasswordSignInMethods();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('can sign in and fetch Refresh Token without `prompt=consent` when always issue Refresh Token is set', async () => {
|
||||||
|
const app = await createApplication('Integration test app', ApplicationType.SPA, {
|
||||||
|
oidcClientMetadata: { redirectUris: [demoAppRedirectUri], postLogoutRedirectUris: [] },
|
||||||
|
customClientMetadata: { alwaysIssueRefreshToken: true },
|
||||||
|
});
|
||||||
|
await validateRefreshToken(app.id, demoAppRedirectUri, true);
|
||||||
|
await deleteApplication(app.id);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('cannot fetch Refresh Token if alwaysIssueRefreshToken is false and prompt is not consent', async () => {
|
||||||
|
const app = await createApplication('Integration test app', ApplicationType.SPA, {
|
||||||
|
oidcClientMetadata: { redirectUris: [demoAppRedirectUri], postLogoutRedirectUris: [] },
|
||||||
|
customClientMetadata: { alwaysIssueRefreshToken: false },
|
||||||
|
});
|
||||||
|
await validateRefreshToken(app.id, demoAppRedirectUri, false);
|
||||||
|
await deleteApplication(app.id);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('cannot fetch Refresh Token for non-web apps', async () => {
|
||||||
|
const redirectUri = 'io.logto://callback';
|
||||||
|
const app = await createApplication('Integration test app', ApplicationType.Native, {
|
||||||
|
oidcClientMetadata: { redirectUris: [redirectUri], postLogoutRedirectUris: [] },
|
||||||
|
customClientMetadata: { alwaysIssueRefreshToken: true },
|
||||||
|
});
|
||||||
|
await validateRefreshToken(app.id, redirectUri, false);
|
||||||
|
await deleteApplication(app.id);
|
||||||
|
});
|
||||||
|
});
|
|
@ -0,0 +1,54 @@
|
||||||
|
import { demoAppApplicationId } from '@logto/schemas';
|
||||||
|
import { trySafe } from '@silverhand/essentials';
|
||||||
|
import { HTTPError, type Headers, got } from 'got';
|
||||||
|
|
||||||
|
import { logtoUrl } from '#src/constants.js';
|
||||||
|
|
||||||
|
describe('content-type: application/json compatibility', () => {
|
||||||
|
const api = got.extend({
|
||||||
|
prefixUrl: new URL('/oidc', logtoUrl),
|
||||||
|
});
|
||||||
|
|
||||||
|
const expectErrorMessageForPayload = async (
|
||||||
|
payload: Record<string, unknown>,
|
||||||
|
errorMessage: string,
|
||||||
|
headers?: Headers
|
||||||
|
) => {
|
||||||
|
return trySafe(
|
||||||
|
api.post('token', {
|
||||||
|
headers,
|
||||||
|
json: payload,
|
||||||
|
}),
|
||||||
|
(error) => {
|
||||||
|
if (!(error instanceof HTTPError)) {
|
||||||
|
return fail('Error is not a HTTPError instance.');
|
||||||
|
}
|
||||||
|
expect(JSON.parse(String(error.response.body))).toHaveProperty(
|
||||||
|
'error_description',
|
||||||
|
errorMessage
|
||||||
|
);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
it('recognizes `application/json` content-type in OIDC token endpoints', async () => {
|
||||||
|
await Promise.all([
|
||||||
|
expectErrorMessageForPayload(
|
||||||
|
{ client_id: demoAppApplicationId },
|
||||||
|
"missing required parameter 'grant_type'"
|
||||||
|
),
|
||||||
|
expectErrorMessageForPayload(
|
||||||
|
{ client_id: demoAppApplicationId, grant_type: 'refresh_token' },
|
||||||
|
"missing required parameter 'refresh_token'"
|
||||||
|
),
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('does not recognize `application/json1` content-type', async () => {
|
||||||
|
await expectErrorMessageForPayload(
|
||||||
|
{ client_id: demoAppApplicationId },
|
||||||
|
'only application/x-www-form-urlencoded content-type bodies are supported on POST /token',
|
||||||
|
{ 'content-type': 'application/json1' }
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
|
@ -26,7 +26,7 @@ describe('get access token', () => {
|
||||||
await enableAllPasswordSignInMethods();
|
await enableAllPasswordSignInMethods();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('sign-in and getAccessToken with admin user', async () => {
|
it('can sign in and getAccessToken with admin user', async () => {
|
||||||
const client = new MockClient({
|
const client = new MockClient({
|
||||||
resources: [defaultManagementApi.resource.indicator],
|
resources: [defaultManagementApi.resource.indicator],
|
||||||
scopes: [defaultManagementApi.scope.name],
|
scopes: [defaultManagementApi.scope.name],
|
||||||
|
@ -49,7 +49,7 @@ describe('get access token', () => {
|
||||||
void expect(client.getAccessToken('api.foo.com')).rejects.toThrow();
|
void expect(client.getAccessToken('api.foo.com')).rejects.toThrow();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('sign-in and getAccessToken with guest user', async () => {
|
it('can sign in and getAccessToken with guest user', async () => {
|
||||||
const client = new MockClient({
|
const client = new MockClient({
|
||||||
resources: [defaultManagementApi.resource.indicator],
|
resources: [defaultManagementApi.resource.indicator],
|
||||||
scopes: [defaultManagementApi.scope.name],
|
scopes: [defaultManagementApi.scope.name],
|
||||||
|
@ -69,7 +69,7 @@ describe('get access token', () => {
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('sign-in and get multiple Access Token by the same Refresh Token within refreshTokenReuseInterval', async () => {
|
it('can sign in and get multiple Access Tokens by the same Refresh Token within refreshTokenReuseInterval', async () => {
|
||||||
const client = new MockClient({ resources: [defaultManagementApi.resource.indicator] });
|
const client = new MockClient({ resources: [defaultManagementApi.resource.indicator] });
|
||||||
|
|
||||||
await client.initSession();
|
await client.initSession();
|
Loading…
Reference in a new issue