This commit is contained in:
Joseph Garrone
2024-06-03 18:28:34 +02:00
parent 37a060c4db
commit c1a63edd71
34 changed files with 834 additions and 1245 deletions

View File

@ -7,7 +7,6 @@ import {
import { replaceImportsInInlineCssCode } from "keycloakify/bin/keycloakify/replacers/replaceImportsInInlineCssCode";
import { same } from "evt/tools/inDepth/same";
import { expect, it, describe } from "vitest";
import { isSameCode } from "../tools/isSameCode";
import {
basenameOfTheKeycloakifyResourcesDir,
nameOfTheGlobal
@ -664,3 +663,10 @@ describe("inline css replacer", () => {
});
});
});
export function isSameCode(code1: string, code2: string): boolean {
const removeSpacesAndNewLines = (code: string) =>
code.replace(/\s/g, "").replace(/\n/g, "");
return removeSpacesAndNewLines(code1) === removeSpacesAndNewLines(code2);
}

View File

@ -1,303 +0,0 @@
import { createGetKcContext } from "keycloakify/login/kcContext/createGetKcContext";
import type { ExtendKcContext } from "keycloakify/login/kcContext/getKcContextFromWindow";
import type { KcContext } from "keycloakify/login/kcContext";
import { same } from "evt/tools/inDepth";
import { assert } from "tsafe/assert";
import type { Equals } from "tsafe";
import {
kcContextMocks,
kcContextCommonMock
} from "keycloakify/login/kcContext/kcContextMocks";
import { deepClone } from "keycloakify/tools/deepClone";
import { expect, it, describe } from "vitest";
describe("createGetKcContext", () => {
const authorizedMailDomains = [
"example.com",
"another-example.com",
"*.yet-another-example.com",
"*.example.com",
"hello-world.com"
];
const displayName = "this is an overwritten common value";
const aNonStandardValue1 = "a non standard value 1";
const aNonStandardValue2 = "a non standard value 2";
type KcContextExtension =
| {
pageId: "register.ftl";
authorizedMailDomains: string[];
}
| {
pageId: "info.ftl";
aNonStandardValue1: string;
}
| {
pageId: "my-extra-page-1.ftl";
}
| {
pageId: "my-extra-page-2.ftl";
aNonStandardValue2: string;
};
const getKcContextProxy = (params: {
mockPageId: ExtendKcContext<KcContextExtension>["pageId"];
}) => {
const { mockPageId } = params;
const { getKcContext } = createGetKcContext<KcContextExtension>({
mockData: [
{
pageId: "login.ftl",
realm: { displayName }
},
{
pageId: "info.ftl",
aNonStandardValue1
},
{
pageId: "register.ftl",
authorizedMailDomains
},
{
pageId: "my-extra-page-2.ftl",
aNonStandardValue2
}
]
});
const { kcContext } = getKcContext({
mockPageId
});
return { kcContext };
};
it("has proper API for login.ftl", () => {
const pageId = "login.ftl";
const { kcContext } = getKcContextProxy({ mockPageId: pageId });
assert(kcContext?.pageId === pageId);
assert<Equals<typeof kcContext, KcContext.Login>>();
expect(
same(
//NOTE: deepClone for printIfExists or other functions...
deepClone(kcContext),
(() => {
const mock = deepClone(
kcContextMocks.find(
({ pageId: pageId_i }) => pageId_i === pageId
)!
);
mock.realm.displayName = displayName;
return mock;
})()
)
).toBe(true);
});
it("has a proper API for info.ftl", () => {
const pageId = "info.ftl";
const { kcContext } = getKcContextProxy({ mockPageId: pageId });
assert(kcContext?.pageId === pageId);
//NOTE: I don't understand the need to add: pageId: typeof pageId; ...
assert<
Equals<
typeof kcContext,
KcContext.Info & {
pageId: typeof pageId;
aNonStandardValue1: string;
}
>
>();
expect(
same(
deepClone(kcContext),
(() => {
const mock = deepClone(
kcContextMocks.find(
({ pageId: pageId_i }) => pageId_i === pageId
)!
);
Object.assign(mock, { aNonStandardValue1 });
return mock;
})()
)
).toBe(true);
});
it("has a proper API for register.ftl", () => {
const pageId = "register.ftl";
const { kcContext } = getKcContextProxy({ mockPageId: pageId });
assert(kcContext?.pageId === pageId);
//NOTE: I don't understand the need to add: pageId: typeof pageId; ...
assert<
Equals<
typeof kcContext,
KcContext.Register & {
pageId: typeof pageId;
authorizedMailDomains: string[];
}
>
>();
expect(
same(
deepClone(kcContext),
(() => {
const mock = deepClone(
kcContextMocks.find(
({ pageId: pageId_i }) => pageId_i === pageId
)!
);
Object.assign(mock, { authorizedMailDomains });
return mock;
})()
)
).toBe(true);
});
it("has a proper API for my-extra-page-2.ftl", () => {
const pageId = "my-extra-page-2.ftl";
const { kcContext } = getKcContextProxy({ mockPageId: pageId });
assert(kcContext?.pageId === pageId);
assert<
Equals<
typeof kcContext,
KcContext.Common & {
pageId: typeof pageId;
aNonStandardValue2: string;
}
>
>();
kcContext.aNonStandardValue2;
expect(
same(
deepClone(kcContext),
(() => {
const mock = deepClone(kcContextCommonMock);
Object.assign(mock, { pageId, aNonStandardValue2 });
return mock;
})()
)
).toBe(true);
});
it("has a proper API for my-extra-page-1.ftl", () => {
const pageId = "my-extra-page-1.ftl";
console.log("We expect a warning here =>");
const { kcContext } = getKcContextProxy({ mockPageId: pageId });
assert(kcContext?.pageId === pageId);
assert<Equals<typeof kcContext, KcContext.Common & { pageId: typeof pageId }>>();
expect(
same(
deepClone(kcContext),
(() => {
const mock = deepClone(kcContextCommonMock);
Object.assign(mock, { pageId });
return mock;
})()
)
).toBe(true);
});
it("returns the proper mock for login.ftl", () => {
const pageId = "login.ftl";
const { getKcContext } = createGetKcContext();
const { kcContext } = getKcContext({
mockPageId: pageId
});
assert<Equals<typeof kcContext, KcContext.Login>>();
assert(
same(
deepClone(kcContext),
deepClone(
kcContextMocks.find(({ pageId: pageId_i }) => pageId_i === pageId)!
)
)
);
});
it("returns undefined when no mock is specified", () => {
const { getKcContext } = createGetKcContext();
const { kcContext } = getKcContext();
assert<Equals<typeof kcContext, KcContext | undefined>>();
assert(kcContext === undefined);
});
it("mock are properly overwritten", () => {
const { getKcContext } = createGetKcContext();
const displayName = "myDisplayName";
const { kcContext } = getKcContext({
mockPageId: "login.ftl",
storyPartialKcContext: {
realm: {
displayName
}
}
});
assert<Equals<typeof kcContext, KcContext.Login>>();
assert(kcContext?.realm.displayName === displayName);
});
it("mockPageId doesn't have to be a singleton", () => {
const { getKcContext } = createGetKcContext();
const mockPageId: "login.ftl" | "register.ftl" = "login.ftl" as any;
const { kcContext } = getKcContext({
mockPageId
});
assert<Equals<typeof kcContext, KcContext.Login | KcContext.Register>>();
});
it("no undefined as long as we provide a mock pageId", () => {
const { getKcContext } = createGetKcContext();
const mockPageId: KcContext["pageId"] = "login.ftl" as any;
const { kcContext } = getKcContext({
mockPageId
});
assert<Equals<typeof kcContext, KcContext>>();
});
});

View File

@ -1,253 +0,0 @@
import { getKcContext } from "keycloakify/login/kcContext/getKcContext";
import type { ExtendKcContext } from "keycloakify/login/kcContext/getKcContextFromWindow";
import type { KcContext } from "keycloakify/login/kcContext";
import { same } from "evt/tools/inDepth";
import { assert } from "tsafe/assert";
import type { Equals } from "tsafe";
import {
kcContextMocks,
kcContextCommonMock
} from "keycloakify/login/kcContext/kcContextMocks";
import { deepClone } from "keycloakify/tools/deepClone";
import { expect, it, describe } from "vitest";
describe("getKcContext", () => {
const authorizedMailDomains = [
"example.com",
"another-example.com",
"*.yet-another-example.com",
"*.example.com",
"hello-world.com"
];
const displayName = "this is an overwritten common value";
const aNonStandardValue1 = "a non standard value 1";
const aNonStandardValue2 = "a non standard value 2";
type KcContextExtension =
| {
pageId: "register.ftl";
authorizedMailDomains: string[];
}
| {
pageId: "info.ftl";
aNonStandardValue1: string;
}
| {
pageId: "my-extra-page-1.ftl";
}
| {
pageId: "my-extra-page-2.ftl";
aNonStandardValue2: string;
};
const getKcContextProxy = (params: {
mockPageId: ExtendKcContext<KcContextExtension>["pageId"];
}) => {
const { mockPageId } = params;
const { kcContext } = getKcContext<KcContextExtension>({
mockPageId,
mockData: [
{
pageId: "login.ftl",
realm: { displayName }
},
{
pageId: "info.ftl",
aNonStandardValue1
},
{
pageId: "register.ftl",
authorizedMailDomains
},
{
pageId: "my-extra-page-2.ftl",
aNonStandardValue2
}
]
});
return { kcContext };
};
it("has proper API for login.ftl", () => {
const pageId = "login.ftl";
const { kcContext } = getKcContextProxy({ mockPageId: pageId });
assert(kcContext?.pageId === pageId);
assert<Equals<typeof kcContext, KcContext.Login>>();
expect(
same(
//NOTE: deepClone for printIfExists or other functions...
deepClone(kcContext),
(() => {
const mock = deepClone(
kcContextMocks.find(
({ pageId: pageId_i }) => pageId_i === pageId
)!
);
mock.realm.displayName = displayName;
return mock;
})()
)
).toBe(true);
});
it("has a proper API for info.ftl", () => {
const pageId = "info.ftl";
const { kcContext } = getKcContextProxy({ mockPageId: pageId });
assert(kcContext?.pageId === pageId);
//NOTE: I don't understand the need to add: pageId: typeof pageId; ...
assert<
Equals<
typeof kcContext,
KcContext.Info & {
pageId: typeof pageId;
aNonStandardValue1: string;
}
>
>();
expect(
same(
deepClone(kcContext),
(() => {
const mock = deepClone(
kcContextMocks.find(
({ pageId: pageId_i }) => pageId_i === pageId
)!
);
Object.assign(mock, { aNonStandardValue1 });
return mock;
})()
)
).toBe(true);
});
it("has a proper API for register.ftl", () => {
const pageId = "register.ftl";
const { kcContext } = getKcContextProxy({ mockPageId: pageId });
assert(kcContext?.pageId === pageId);
//NOTE: I don't understand the need to add: pageId: typeof pageId; ...
assert<
Equals<
typeof kcContext,
KcContext.Register & {
pageId: typeof pageId;
authorizedMailDomains: string[];
}
>
>();
expect(
same(
deepClone(kcContext),
(() => {
const mock = deepClone(
kcContextMocks.find(
({ pageId: pageId_i }) => pageId_i === pageId
)!
);
Object.assign(mock, { authorizedMailDomains });
return mock;
})()
)
).toBe(true);
});
it("has a proper API for my-extra-page-2.ftl", () => {
const pageId = "my-extra-page-2.ftl";
const { kcContext } = getKcContextProxy({ mockPageId: pageId });
assert(kcContext?.pageId === pageId);
assert<
Equals<
typeof kcContext,
KcContext.Common & {
pageId: typeof pageId;
aNonStandardValue2: string;
}
>
>();
kcContext.aNonStandardValue2;
expect(
same(
deepClone(kcContext),
(() => {
const mock = deepClone(kcContextCommonMock);
Object.assign(mock, { pageId, aNonStandardValue2 });
return mock;
})()
)
).toBe(true);
});
it("has a proper API for my-extra-page-1.ftl", () => {
const pageId = "my-extra-page-1.ftl";
console.log("We expect a warning here =>");
const { kcContext } = getKcContextProxy({ mockPageId: pageId });
assert(kcContext?.pageId === pageId);
assert<Equals<typeof kcContext, KcContext.Common & { pageId: typeof pageId }>>();
expect(
same(
deepClone(kcContext),
(() => {
const mock = deepClone(kcContextCommonMock);
Object.assign(mock, { pageId });
return mock;
})()
)
).toBe(true);
});
it("returns the proper mock for login.ftl", () => {
const pageId = "login.ftl";
const { kcContext } = getKcContext({
mockPageId: pageId
});
assert<Equals<typeof kcContext, KcContext | undefined>>();
assert(
same(
deepClone(kcContext),
deepClone(
kcContextMocks.find(({ pageId: pageId_i }) => pageId_i === pageId)!
)
)
);
});
it("returns undefined when no mock is specified", () => {
const { kcContext } = getKcContext();
assert<Equals<typeof kcContext, KcContext | undefined>>();
assert(kcContext === undefined);
});
});

View File

@ -1,100 +0,0 @@
import { AndByDiscriminatingKey } from "keycloakify/tools/AndByDiscriminatingKey";
import { assert } from "tsafe/assert";
import type { Equals } from "tsafe";
{
type Base =
| { pageId: "a"; onlyA: string }
| { pageId: "b"; onlyB: string }
| { pageId: "only base"; onlyBase: string };
type Extension =
| { pageId: "a"; onlyExtA: string }
| { pageId: "b"; onlyExtB: string }
| { pageId: "only ext"; onlyExt: string };
type Got = AndByDiscriminatingKey<"pageId", Extension, Base>;
type Expected =
| { pageId: "a"; onlyA: string; onlyExtA: string }
| { pageId: "b"; onlyB: string; onlyExtB: string }
| { pageId: "only base"; onlyBase: string }
| { pageId: "only ext"; onlyExt: string };
assert<Equals<Got, Expected>>();
const x: Got = null as any;
if (x.pageId === "a") {
x.onlyA;
x.onlyExtA;
//@ts-expect-error
x.onlyB;
//@ts-expect-error
x.onlyBase;
//@ts-expect-error
x.onlyExt;
}
if (x.pageId === "b") {
x.onlyB;
x.onlyExtB;
//@ts-expect-error
x.onlyA;
//@ts-expect-error
x.onlyBase;
//@ts-expect-error
x.onlyExt;
}
if (x.pageId === "only base") {
x.onlyBase;
//@ts-expect-error
x.onlyA;
//@ts-expect-error
x.onlyB;
//@ts-expect-error
x.onlyExt;
}
if (x.pageId === "only ext") {
x.onlyExt;
//@ts-expect-error
x.onlyA;
//@ts-expect-error
x.onlyB;
//@ts-expect-error
x.onlyBase;
}
}
{
type Base =
| { pageId: "a"; onlyA: string }
| { pageId: "b"; onlyB: string }
| { pageId: "only base"; onlyBase: string };
type Extension = { pageId: "only ext"; onlyExt: string };
type Got = AndByDiscriminatingKey<"pageId", Extension, Base>;
type Expected =
| { pageId: "a"; onlyA: string }
| { pageId: "b"; onlyB: string }
| { pageId: "only base"; onlyBase: string }
| { pageId: "only ext"; onlyExt: string };
assert<Equals<Got, Expected>>();
}

View File

@ -0,0 +1,211 @@
import { type ExtendKcContext, createGetKcContextMock } from "keycloakify/login";
import { KcContext as KcContextBase } from "keycloakify/login/kcContext/KcContext";
import { assert, type Equals } from "tsafe/assert";
import { Reflect } from "tsafe/Reflect";
{
type KcContextExtraProperties = {
properties: {
myCustomProperty: string | undefined;
};
};
type KcContextExtraPropertiesPerPage = {
"login.ftl": {
foo: string;
};
"my-custom-page.ftl": {
bar: number;
};
};
type KcContext = ExtendKcContext<
KcContextExtraProperties,
KcContextExtraPropertiesPerPage
>;
{
type Got = Extract<KcContext, { pageId: "login.ftl" }>;
type Expected = KcContextBase.Login & {
properties: { myCustomProperty: string | undefined };
} & { foo: string };
assert<Equals<Got, Expected>>();
}
{
type Got = Extract<KcContext, { pageId: "register.ftl" }>;
type Expected = KcContextBase.Register & {
properties: { myCustomProperty: string | undefined };
};
assert<Equals<Got, Expected>>();
}
{
type Got = Extract<KcContext, { pageId: "my-custom-page.ftl" }>;
type Expected = KcContextBase.Common &
KcContextExtraProperties & { pageId: "my-custom-page.ftl" } & {
properties: { myCustomProperty: string | undefined };
} & { bar: number };
assert<Got extends Expected ? true : false>();
assert<Expected extends Got ? true : false>();
}
const { getKcContextMock } = createGetKcContextMock({
kcContextExtraProperties: Reflect<KcContextExtraProperties>(),
kcContextExtraPropertiesPerPage: Reflect<KcContextExtraPropertiesPerPage>()
});
{
const got = getKcContextMock({
pageId: "login.ftl"
});
type Expected = Extract<KcContext, { pageId: "login.ftl" }>;
assert<Equals<typeof got, Expected>>();
}
{
const got = getKcContextMock({
pageId: "register.ftl"
});
type Expected = Extract<KcContext, { pageId: "register.ftl" }>;
assert<Equals<typeof got, Expected>>();
}
{
const got = getKcContextMock({
pageId: "my-custom-page.ftl"
});
type Expected = Extract<KcContext, { pageId: "my-custom-page.ftl" }>;
assert<Equals<typeof got, Expected>>();
}
getKcContextMock({
// @ts-expect-error
pageId: "non-existing-page.ftl"
});
getKcContextMock({
pageId: "login.ftl",
overrides: {
// @ts-expect-error
bar: 42
}
});
createGetKcContextMock({
kcContextExtraProperties: Reflect<KcContextExtraProperties>(),
kcContextExtraPropertiesPerPage: Reflect<KcContextExtraPropertiesPerPage>(),
overrides: {
locale: {
currentLanguageTag: "fr"
},
// @ts-expect-error
profile: {}
},
overridesPerPage: {
"register.ftl": {
profile: {
attributesByName: {
username: {
validators: {
pattern: {
pattern: "^[a-zA-Z0-9]+$",
"ignore.empty.value": true,
"error-message": "${alphanumericalCharsOnly}"
}
},
value: undefined,
name: "username"
}
}
}
},
// @ts-expect-error
"non-existing-page.ftl": {}
}
});
createGetKcContextMock({
kcContextExtraProperties: Reflect<KcContextExtraProperties>(),
kcContextExtraPropertiesPerPage: Reflect<KcContextExtraPropertiesPerPage>(),
overridesPerPage: {
"register.ftl": {
// @ts-expect-error
nonExistingProperty: 42
}
}
});
}
{
type KcContextExtraProperties = {};
type KcContextExtraPropertiesPerPage = {};
type KcContext = ExtendKcContext<
KcContextExtraProperties,
KcContextExtraPropertiesPerPage
>;
{
type Got = Extract<KcContext, { pageId: "login.ftl" }>;
type Expected = KcContextBase.Login;
assert<Equals<Got, Expected>>();
}
{
type Got = Extract<KcContext, { pageId: "register.ftl" }>;
type Expected = KcContextBase.Register;
assert<Equals<Got, Expected>>();
}
const { getKcContextMock } = createGetKcContextMock({
kcContextExtraProperties: Reflect<KcContextExtraProperties>(),
kcContextExtraPropertiesPerPage: Reflect<KcContextExtraPropertiesPerPage>()
});
{
const got = getKcContextMock({
pageId: "login.ftl"
});
type Expected = Extract<KcContext, { pageId: "login.ftl" }>;
assert<Equals<typeof got, Expected>>();
}
{
const got = getKcContextMock({
pageId: "register.ftl"
});
type Expected = Extract<KcContext, { pageId: "register.ftl" }>;
assert<Equals<typeof got, Expected>>();
}
getKcContextMock({
// @ts-expect-error
pageId: "non-existing-page.ftl"
});
getKcContextMock({
pageId: "login.ftl",
overrides: {
// @ts-expect-error
bar: 42
}
});
}

View File

@ -0,0 +1,202 @@
import { createGetKcContextMock, type Attribute } from "keycloakify/login";
import { id } from "tsafe/id";
import {
kcContextMocks,
kcContextCommonMock
} from "keycloakify/login/kcContext/kcContextMocks";
import { structuredCloneButFunctions } from "keycloakify/tools/structuredCloneButFunctions";
import { expect, it, describe } from "vitest";
describe("createGetKcContextMock", () => {
type KcContextExtraProperties = {
properties: {
MY_ENV_VAR?: string;
};
};
type KcContextExtraPropertiesPerPage = {
"register.ftl": {
authorizedMailDomains: string[];
};
"my-plugin-page.ftl": {
aCustomValue: string;
};
};
const { getKcContextMock } = createGetKcContextMock({
kcContextExtraProperties: id<KcContextExtraProperties>({
properties: {
MY_ENV_VAR: "my env var value"
}
}),
kcContextExtraPropertiesPerPage: id<KcContextExtraPropertiesPerPage>({
"register.ftl": {
authorizedMailDomains: ["gmail.com", "hotmail.com"]
},
"my-plugin-page.ftl": {
aCustomValue: "some value"
}
}),
overrides: {
locale: {
currentLanguageTag: "fr"
}
},
overridesPerPage: {
"register.ftl": {
profile: {
attributesByName: {
username: {
validators: {
pattern: {
pattern: "^[a-zA-Z0-9]+$",
"ignore.empty.value": true,
"error-message": "${alphanumericalCharsOnly}"
}
},
value: undefined,
name: "username"
}
}
},
passwordPolicies: {
length: 66
}
}
}
});
it("returns the proper mock for register.frl", () => {
const got = getKcContextMock({
pageId: "register.ftl",
overrides: {
profile: {
attributesByName: {
gender: id<Attribute>({
validators: {
options: {
options: [
"male",
"female",
"non-binary",
"prefer-not-to-say"
]
}
},
displayName: "${gender}",
annotations: {},
required: true,
readOnly: false,
name: "gender"
}),
email: undefined
}
}
}
});
const expected = (() => {
const out: any = structuredCloneButFunctions(
kcContextMocks.find(({ pageId }) => pageId === "register.ftl")
);
out.properties = {
MY_ENV_VAR: "my env var value"
};
out.authorizedMailDomains = ["gmail.com", "hotmail.com"];
out.locale.currentLanguageTag = "fr";
delete out.profile.attributesByName.email;
{
const usernameAttribute = out.profile.attributesByName.username;
delete usernameAttribute.value;
usernameAttribute.validators.pattern = {
pattern: "^[a-zA-Z0-9]+$",
"ignore.empty.value": true,
"error-message": "${alphanumericalCharsOnly}"
};
}
out.profile.attributesByName.gender = {
validators: {
options: {
options: ["male", "female", "non-binary", "prefer-not-to-say"]
}
},
displayName: "${gender}",
annotations: {},
required: true,
readOnly: false,
name: "gender"
};
(out.passwordPolicies ??= {}).length = 66;
return out;
})();
expect(got).toEqual(expected);
});
it("returns the proper mock plugin pages", () => {
const got = getKcContextMock({
pageId: "my-plugin-page.ftl",
overrides: {
locale: {
currentLanguageTag: "en"
}
}
});
const expected = (() => {
const out: any = structuredCloneButFunctions(kcContextCommonMock);
out.pageId = "my-plugin-page.ftl";
out.aCustomValue = "some value";
out.properties = {
MY_ENV_VAR: "my env var value"
};
out.locale.currentLanguageTag = "en";
return out;
})();
expect(got).toEqual(expected);
});
it("returns the proper mock for other pages", () => {
const got = getKcContextMock({
pageId: "login.ftl",
overrides: {
realm: {
registrationAllowed: false
}
}
});
const expected = (() => {
const out: any = structuredCloneButFunctions(
kcContextMocks.find(({ pageId }) => pageId === "login.ftl")
);
out.properties = {
MY_ENV_VAR: "my env var value"
};
out.locale.currentLanguageTag = "fr";
out.realm.registrationAllowed = false;
return out;
})();
expect(got).toEqual(expected);
});
});

View File

@ -1,6 +0,0 @@
export function isSameCode(code1: string, code2: string): boolean {
const removeSpacesAndNewLines = (code: string) =>
code.replace(/\s/g, "").replace(/\n/g, "");
return removeSpacesAndNewLines(code1) === removeSpacesAndNewLines(code2);
}