Compare commits

...

5 Commits

Author SHA1 Message Date
f0256a0535 As alway, prettier has opinions 2024-07-16 14:04:23 -07:00
142a985914 Tightened the language. 2024-07-16 13:56:53 -07:00
a8531d498a Tests are updated and working. Had to revise the 'search-select' binding to work with the new search-select. 2024-07-16 13:49:17 -07:00
f8cb4e880b web: roll back update to sonar
Bloody dependabot.  We're not compatible with ESLint 9 yet, darnit, and yet
dependabot keeps pushing upgrades on us.
2024-07-16 09:23:28 -07:00
3ced637db3 web: grammar fix and lint update
1. Merged the two SonarJS lints into one
2. Fixed a grammatical error in RedirectStage
2024-07-16 09:19:57 -07:00
18 changed files with 99 additions and 107 deletions

View File

@ -53,7 +53,7 @@ type Pair = [string, string];
// Define a getter for each provider type in the radio button collection. // Define a getter for each provider type in the radio button collection.
const providerValues: Pair[] = [ const providerValues: Pair[] = [
["oauth2provider", "oauth2Provider"], ["oauth2Provider", "oauth2Provider"],
["ldapprovider", "ldapProvider"], ["ldapprovider", "ldapProvider"],
["proxyprovider-proxy", "proxyProviderProxy"], ["proxyprovider-proxy", "proxyProviderProxy"],
["proxyprovider-forwardsingle", "proxyProviderForwardsingle"], ["proxyprovider-forwardsingle", "proxyProviderForwardsingle"],
@ -66,7 +66,7 @@ providerValues.forEach(([value, name]: Pair) => {
Object.defineProperties(ApplicationWizardView.prototype, { Object.defineProperties(ApplicationWizardView.prototype, {
[name]: { [name]: {
get: function () { get: function () {
return this.providerList.$(`>>>input[value="${value}"]`); return this.providerList.$(`>>>div[data-testid=wizard-provider-${value}]`);
}, },
}, },
}); });

View File

@ -4,9 +4,9 @@ import { $ } from "@wdio/globals";
export class ForwardProxyForm extends Page { export class ForwardProxyForm extends Page {
async setAuthorizationFlow(selector: string) { async setAuthorizationFlow(selector: string) {
await this.searchSelect( await this.searchSelect(
'>>>ak-flow-search[name="authorizationFlow"] input[type="text"]', '[name="authorizationFlow"]',
"authorizationFlow", "authorizationFlow",
`button*=${selector}`, `div*=${selector}`,
); );
} }

View File

@ -3,9 +3,9 @@ import Page from "../page.js";
export class LdapForm extends Page { export class LdapForm extends Page {
async setBindFlow(selector: string) { async setBindFlow(selector: string) {
await this.searchSelect( await this.searchSelect(
'>>>ak-branded-flow-search[name="authorizationFlow"] input[type="text"]', "[name=authorizationFlow]",
"authorizationFlow", "authorizationFlow",
`button*=${selector}`, `div*=${selector}`,
); );
} }
} }

View File

@ -4,9 +4,9 @@ import { $ } from "@wdio/globals";
export class OauthForm extends Page { export class OauthForm extends Page {
async setAuthorizationFlow(selector: string) { async setAuthorizationFlow(selector: string) {
await this.searchSelect( await this.searchSelect(
'>>>ak-flow-search[name="authorizationFlow"] input[type="text"]', '[name="authorizationFlow"]',
"authorizationFlow", "authorizationFlow",
`button*=${selector}`, `div*=${selector}`,
); );
} }

View File

@ -3,9 +3,9 @@ import Page from "../page.js";
export class RadiusForm extends Page { export class RadiusForm extends Page {
async setAuthenticationFlow(selector: string) { async setAuthenticationFlow(selector: string) {
await this.searchSelect( await this.searchSelect(
'>>>ak-branded-flow-search[name="authorizationFlow"] input[type="text"]', '[name="authorizationFlow"]',
"authorizationFlow", "authorizationFlow",
`button*=${selector}`, `div*=${selector}`,
); );
} }
} }

View File

@ -4,9 +4,9 @@ import { $ } from "@wdio/globals";
export class SamlForm extends Page { export class SamlForm extends Page {
async setAuthorizationFlow(selector: string) { async setAuthorizationFlow(selector: string) {
await this.searchSelect( await this.searchSelect(
'>>>ak-flow-search[name="authorizationFlow"] input[type="text"]', '[name="authorizationFlow"]',
"authorizationFlow", "authorizationFlow",
`button*=${selector}`, `div*=${selector}`,
); );
} }

View File

@ -4,9 +4,9 @@ import { $ } from "@wdio/globals";
export class TransparentProxyForm extends Page { export class TransparentProxyForm extends Page {
async setAuthorizationFlow(selector: string) { async setAuthorizationFlow(selector: string) {
await this.searchSelect( await this.searchSelect(
'>>>ak-flow-search[name="authorizationFlow"] input[type="text"]', '[name="authorizationFlow"]',
"authorizationFlow", "authorizationFlow",
`button*=${selector}`, `div*=${selector}`,
); );
} }

View File

@ -1,6 +1,7 @@
import Page from "./page.js"; import Page from "./page.js";
import UserLibraryPage from "./user-library.page.js"; import UserLibraryPage from "./user-library.page.js";
import { $ } from "@wdio/globals"; import { $ } from "@wdio/globals";
import { and, or, presenceOf, textToBePresentInElement } from "wdio-wait-for";
/** /**
* sub page containing specific selectors and methods for a specific page * sub page containing specific selectors and methods for a specific page
@ -48,6 +49,14 @@ class LoginPage extends Page {
await this.pause(); await this.pause();
await this.password(password); await this.password(password);
await this.pause(); await this.pause();
const redirect = await $(">>>a[type=submit]");
await browser.waitUntil(or(presenceOf(redirect), presenceOf(UserLibraryPage.pageHeader)));
if (await redirect.isExisting()) {
await redirect.click();
}
await this.pause(">>>div.header h1"); await this.pause(">>>div.header h1");
return UserLibraryPage; return UserLibraryPage;
} }

View File

@ -32,10 +32,16 @@ export default class Page {
*/ */
async searchSelect(searchSelector: string, managedSelector: string, buttonSelector: string) { async searchSelect(searchSelector: string, managedSelector: string, buttonSelector: string) {
const inputBind = await $(searchSelector); const controlSelector = `>>>ak-search-select-view${searchSelector}`;
const control = await $(controlSelector);
control.scrollIntoView();
const inputBind = await control.$(">>>input[type=text]");
await inputBind.click(); await inputBind.click();
const searchBlock = await $(`>>>div[data-managed-for="${managedSelector}"]`); const searchBlock = await $(`>>>div[data-managed-for="${managedSelector}"]`);
const target = searchBlock.$(buttonSelector); const interior = searchBlock.$(">>>ul");
interior.scrollIntoView();
const target = interior.$(buttonSelector);
return await target.click(); return await target.click();
} }

View File

@ -1,10 +1,7 @@
import LoginPage from "../pageobjects/login.page.js"; import LoginPage from "../pageobjects/login.page.js";
import UserLibraryPage from "../pageobjects/user-library.page.js";
import { GOOD_PASSWORD, GOOD_USERNAME } from "./constants.js"; import { GOOD_PASSWORD, GOOD_USERNAME } from "./constants.js";
import { expect } from "@wdio/globals";
export const login = async () => { export const login = async () => {
await LoginPage.open(); await LoginPage.open();
await LoginPage.login(GOOD_USERNAME, GOOD_PASSWORD); await LoginPage.login(GOOD_USERNAME, GOOD_PASSWORD);
await expect(UserLibraryPage.pageHeader).toHaveText("My applications");
}; };

View File

@ -212,7 +212,9 @@ export const config: Options.Testrunner = {
* @param {Array.<String>} specs List of spec file paths that are to be run * @param {Array.<String>} specs List of spec file paths that are to be run
* @param {object} browser instance of created browser/device session * @param {object} browser instance of created browser/device session
*/ */
before: function (_capabilities, _specs) {}, before: function (_capabilities, _specs) {
browser.setWindowSize(1920, 1080);
},
/** /**
* Runs before a WebdriverIO command gets executed. * Runs before a WebdriverIO command gets executed.
* @param {string} commandName hook command name * @param {string} commandName hook command name

10
web/package-lock.json generated
View File

@ -93,7 +93,7 @@
"eslint-config-google": "^0.14.0", "eslint-config-google": "^0.14.0",
"eslint-plugin-custom-elements": "0.0.8", "eslint-plugin-custom-elements": "0.0.8",
"eslint-plugin-lit": "^1.11.0", "eslint-plugin-lit": "^1.11.0",
"eslint-plugin-sonarjs": "^1.0.3", "eslint-plugin-sonarjs": "0.25.1",
"eslint-plugin-storybook": "^0.8.0", "eslint-plugin-storybook": "^0.8.0",
"github-slugger": "^2.0.0", "github-slugger": "^2.0.0",
"glob": "^11.0.0", "glob": "^11.0.0",
@ -14421,15 +14421,15 @@
"license": "MIT" "license": "MIT"
}, },
"node_modules/eslint-plugin-sonarjs": { "node_modules/eslint-plugin-sonarjs": {
"version": "1.0.3", "version": "0.25.1",
"resolved": "https://registry.npmjs.org/eslint-plugin-sonarjs/-/eslint-plugin-sonarjs-1.0.3.tgz", "resolved": "https://registry.npmjs.org/eslint-plugin-sonarjs/-/eslint-plugin-sonarjs-0.25.1.tgz",
"integrity": "sha512-6s41HLPYPyDrp+5+7Db5yFYbod6h9pC7yx+xfcNwHRcLe1EZwbbQT/tdOAkR7ekVUkNGEvN3GmYakIoQUX7dEg==", "integrity": "sha512-5IOKvj/GMBNqjxBdItfotfRHo7w48496GOu1hxdeXuD0mB1JBlDCViiLHETDTfA8pDAVSBimBEQoetRXYceQEw==",
"dev": true, "dev": true,
"engines": { "engines": {
"node": ">=16" "node": ">=16"
}, },
"peerDependencies": { "peerDependencies": {
"eslint": "^8.0.0 || ^9.0.0" "eslint": "^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0"
} }
}, },
"node_modules/eslint-plugin-storybook": { "node_modules/eslint-plugin-storybook": {

View File

@ -120,7 +120,7 @@
"eslint-config-google": "^0.14.0", "eslint-config-google": "^0.14.0",
"eslint-plugin-custom-elements": "0.0.8", "eslint-plugin-custom-elements": "0.0.8",
"eslint-plugin-lit": "^1.11.0", "eslint-plugin-lit": "^1.11.0",
"eslint-plugin-sonarjs": "^1.0.3", "eslint-plugin-sonarjs": "0.25.1",
"eslint-plugin-storybook": "^0.8.0", "eslint-plugin-storybook": "^0.8.0",
"github-slugger": "^2.0.0", "github-slugger": "^2.0.0",
"glob": "^11.0.0", "glob": "^11.0.0",

View File

@ -1,55 +0,0 @@
import { execFileSync } from "child_process";
import { ESLint } from "eslint";
import path from "path";
import process from "process";
// Code assumes this script is in the './web/scripts' folder.
const projectRoot = execFileSync("git", ["rev-parse", "--show-toplevel"], {
encoding: "utf8",
}).replace("\n", "");
process.chdir(path.join(projectRoot, "./web"));
const eslintConfig = {
overrideConfig: {
env: {
browser: true,
es2021: true,
},
extends: [
"eslint:recommended",
"plugin:@typescript-eslint/recommended",
"plugin:lit/recommended",
"plugin:custom-elements/recommended",
"plugin:storybook/recommended",
"plugin:sonarjs/recommended",
],
parser: "@typescript-eslint/parser",
parserOptions: {
ecmaVersion: 12,
sourceType: "module",
},
plugins: ["@typescript-eslint", "lit", "custom-elements", "sonarjs"],
rules: {
"indent": "off",
"linebreak-style": ["error", "unix"],
"quotes": ["error", "double", { avoidEscape: true }],
"semi": ["error", "always"],
"@typescript-eslint/ban-ts-comment": "off",
"sonarjs/cognitive-complexity": ["error", 9],
"sonarjs/no-duplicate-string": "off",
"sonarjs/no-nested-template-literals": "off",
},
},
};
const updated = ["./src/", "./build.mjs", "./scripts/*.mjs"];
const eslint = new ESLint(eslintConfig);
const results = await eslint.lintFiles(updated);
const formatter = await eslint.loadFormatter("stylish");
const resultText = formatter.format(results);
const errors = results.reduce((acc, result) => acc + result.errorCount, 0);
// eslint-disable-next-line no-console
console.log(resultText);
process.exit(errors > 1 ? 1 : 0);

View File

@ -43,34 +43,53 @@ const eslintConfig = {
}, },
}; };
const porcelainV1 = /^(..)\s+(.*$)/; function findChangedFiles() {
const gitStatus = execFileSync("git", ["status", "--porcelain", "."], { encoding: "utf8" }); const porcelainV1 = /^(..)\s+(.*$)/;
const gitStatus = execFileSync("git", ["status", "--porcelain", "."], { encoding: "utf8" });
const statuses = gitStatus.split("\n").reduce((acc, line) => { const statuses = gitStatus.split("\n").reduce((acc, line) => {
const match = porcelainV1.exec(line.replace("\n")); const match = porcelainV1.exec(line.replace("\n"));
if (!match) { if (!match) {
return acc; return acc;
} }
const [status, path] = Array.from(match).slice(1, 3); const [status, path] = Array.from(match).slice(1, 3);
return [...acc, [status, path.split("\x00")[0]]]; return [...acc, [status, path.split("\x00")[0]]];
}, []); }, []);
const isModified = /^(M|\?|\s)(M|\?|\s)/; const isModified = /^(M|\?|\s)(M|\?|\s)/;
const modified = (s) => isModified.test(s); const modified = (s) => isModified.test(s);
const isCheckable = /\.(ts|js|mjs)$/; const isCheckable = /\.(ts|js|mjs)$/;
const checkable = (s) => isCheckable.test(s); const checkable = (s) => isCheckable.test(s);
const ignored = /\/\.storybook\//; const ignored = /\/\.storybook\//;
const notIgnored = (s) => !ignored.test(s); const notIgnored = (s) => !ignored.test(s);
const updated = statuses.reduce( return statuses.reduce(
(acc, [status, filename]) => (acc, [status, filename]) =>
modified(status) && checkable(filename) && notIgnored(filename) modified(status) && checkable(filename) && notIgnored(filename)
? [...acc, path.join(projectRoot, filename)] ? [...acc, path.join(projectRoot, filename)]
: acc, : acc,
[], [],
); );
}
if (process.argv.length > 2 && (process.argv[2] === "-h" || process.argv[2] === "--help")) {
// eslint-disable-next-line no-console
console.log(`Run eslint with extra-hard checks
options:
-c, --changed: (default) check only the files that have changed
-n, --nightmare: check all the files in the repository
-h, --help: This help message
`);
process.exit(0);
}
const updated =
process.argv.length > 2 && (process.argv[2] === "-n" || process.argv[2] === "--nightmare")
? ["./src/", "./build.mjs", "./scripts/*.mjs"]
: findChangedFiles();
const eslint = new ESLint(eslintConfig); const eslint = new ESLint(eslintConfig);
const results = await eslint.lintFiles(updated); const results = await eslint.lintFiles(updated);

View File

@ -30,6 +30,7 @@ export type LocalTypeCreate = TypeCreate & {
modelName: ProviderModelEnumType; modelName: ProviderModelEnumType;
converter: ModelConverter; converter: ModelConverter;
note?: ProviderNote; note?: ProviderNote;
testId: string;
renderer: ProviderRenderer; renderer: ProviderRenderer;
}; };
@ -46,6 +47,7 @@ export const providerModelsList: LocalTypeCreate[] = [
...(provider as OAuth2ProviderRequest), ...(provider as OAuth2ProviderRequest),
}), }),
component: "", component: "",
testId: "wizard-provider-oauth2Provider",
iconUrl: "/static/authentik/sources/openidconnect.svg", iconUrl: "/static/authentik/sources/openidconnect.svg",
}, },
{ {
@ -62,6 +64,7 @@ export const providerModelsList: LocalTypeCreate[] = [
...(provider as LDAPProviderRequest), ...(provider as LDAPProviderRequest),
}), }),
component: "", component: "",
testId: "wizard-provider-ldapprovider",
iconUrl: "/static/authentik/sources/ldap.png", iconUrl: "/static/authentik/sources/ldap.png",
}, },
{ {
@ -77,6 +80,7 @@ export const providerModelsList: LocalTypeCreate[] = [
mode: ProxyMode.Proxy, mode: ProxyMode.Proxy,
}), }),
component: "", component: "",
testId: "wizard-provider-proxyprovider-proxy",
iconUrl: "/static/authentik/sources/proxy.svg", iconUrl: "/static/authentik/sources/proxy.svg",
}, },
{ {
@ -92,6 +96,7 @@ export const providerModelsList: LocalTypeCreate[] = [
mode: ProxyMode.ForwardSingle, mode: ProxyMode.ForwardSingle,
}), }),
component: "", component: "",
testId: "wizard-provider-proxyprovider-forwardsingle",
iconUrl: "/static/authentik/sources/proxy.svg", iconUrl: "/static/authentik/sources/proxy.svg",
}, },
{ {
@ -107,6 +112,7 @@ export const providerModelsList: LocalTypeCreate[] = [
mode: ProxyMode.ForwardDomain, mode: ProxyMode.ForwardDomain,
}), }),
component: "", component: "",
testId: "wizard-provider-proxyprovider-forwarddomain",
iconUrl: "/static/authentik/sources/proxy.svg", iconUrl: "/static/authentik/sources/proxy.svg",
}, },
{ {
@ -123,6 +129,7 @@ export const providerModelsList: LocalTypeCreate[] = [
note: () => html`<ak-license-notice></ak-license-notice>`, note: () => html`<ak-license-notice></ak-license-notice>`,
requiresEnterprise: true, requiresEnterprise: true,
component: "", component: "",
testId: "wizard-provider-racprovider",
iconUrl: "/static/authentik/sources/rac.svg", iconUrl: "/static/authentik/sources/rac.svg",
}, },
{ {
@ -137,6 +144,7 @@ export const providerModelsList: LocalTypeCreate[] = [
...(provider as SAMLProviderRequest), ...(provider as SAMLProviderRequest),
}), }),
component: "", component: "",
testId: "wizard-provider-samlprovider",
iconUrl: "/static/authentik/sources/saml.png", iconUrl: "/static/authentik/sources/saml.png",
}, },
{ {
@ -151,6 +159,7 @@ export const providerModelsList: LocalTypeCreate[] = [
...(provider as RadiusProviderRequest), ...(provider as RadiusProviderRequest),
}), }),
component: "", component: "",
testId: "wizard-provider-radiusprovider",
iconUrl: "/static/authentik/sources/radius.svg", iconUrl: "/static/authentik/sources/radius.svg",
}, },
{ {
@ -165,6 +174,7 @@ export const providerModelsList: LocalTypeCreate[] = [
...(provider as SCIMProviderRequest), ...(provider as SCIMProviderRequest),
}), }),
component: "", component: "",
testId: "wizard-provider-scimprovider",
iconUrl: "/static/authentik/sources/scim.png", iconUrl: "/static/authentik/sources/scim.png",
}, },
]; ];

View File

@ -5,6 +5,7 @@ import { WizardPage } from "@goauthentik/elements/wizard/WizardPage";
import { msg, str } from "@lit/localize"; import { msg, str } from "@lit/localize";
import { CSSResult, TemplateResult, css, html, nothing } from "lit"; import { CSSResult, TemplateResult, css, html, nothing } from "lit";
import { customElement, property } from "lit/decorators.js"; import { customElement, property } from "lit/decorators.js";
import { ifDefined } from "lit/directives/if-defined.js";
import PFCard from "@patternfly/patternfly/components/Card/card.css"; import PFCard from "@patternfly/patternfly/components/Card/card.css";
import PFForm from "@patternfly/patternfly/components/Form/form.css"; import PFForm from "@patternfly/patternfly/components/Form/form.css";
@ -19,10 +20,12 @@ export enum TypeCreateWizardPageLayouts {
grid = "grid", grid = "grid",
} }
type TypeCreateWithTestId = TypeCreate & { testId?: string };
@customElement("ak-wizard-page-type-create") @customElement("ak-wizard-page-type-create")
export class TypeCreateWizardPage extends WithLicenseSummary(WizardPage) { export class TypeCreateWizardPage extends WithLicenseSummary(WizardPage) {
@property({ attribute: false }) @property({ attribute: false })
types: TypeCreate[] = []; types: TypeCreateWithTestId[] = [];
@property({ attribute: false }) @property({ attribute: false })
selectedType?: TypeCreate; selectedType?: TypeCreate;
@ -51,7 +54,7 @@ export class TypeCreateWizardPage extends WithLicenseSummary(WizardPage) {
sidebarLabel = () => msg("Select type"); sidebarLabel = () => msg("Select type");
activeCallback: () => Promise<void> = async () => { activeCallback = async () => {
this.host.isValid = false; this.host.isValid = false;
if (this.selectedType) { if (this.selectedType) {
this.selectDispatch(this.selectedType); this.selectDispatch(this.selectedType);
@ -78,6 +81,7 @@ export class TypeCreateWizardPage extends WithLicenseSummary(WizardPage) {
: "pf-m-selectable-raised"} ${this.selectedType == type : "pf-m-selectable-raised"} ${this.selectedType == type
? "pf-m-selected-raised" ? "pf-m-selected-raised"
: ""}" : ""}"
data-testid=${ifDefined(type.testId)}
tabindex=${idx} tabindex=${idx}
@click=${() => { @click=${() => {
if (requiresEnterprise) { if (requiresEnterprise) {

View File

@ -79,7 +79,7 @@ export class RedirectStage extends BaseStage<RedirectChallenge, FlowChallengeRes
<div class="pf-c-login__main-body"> <div class="pf-c-login__main-body">
<form class="pf-c-form"> <form class="pf-c-form">
<div class="pf-c-form__group"> <div class="pf-c-form__group">
<p>${msg("You're about to be redirect to the following URL.")}</p> <p>${msg("You will now be redirected to the following URL.")}</p>
<code>${this.getURL()}</code> <code>${this.getURL()}</code>
</div> </div>
<div class="pf-c-form__group pf-m-action"> <div class="pf-c-form__group pf-m-action">