diff --git a/web/src/client/software.js b/web/src/client/software.js index 5fae2a2002..e5a5fd0ad2 100644 --- a/web/src/client/software.js +++ b/web/src/client/software.js @@ -25,42 +25,6 @@ import { WithProgress, WithStatus } from "./mixins"; const SOFTWARE_SERVICE = "org.opensuse.Agama.Software1"; -/** - * @typedef {object} Product - * @property {string} id - Product ID (e.g., "Leap") - * @property {string} name - Product name (e.g., "openSUSE Leap 15.4") - * @property {string} description - Product description - */ - -/** - * @typedef {object} ActionResult - * @property {boolean} success - Whether the action was successfully done. - * @property {string} message - Result message. - */ - -/** - * @typedef {object} SoftwareProposal - * @property {string} size - Used space in human-readable form. - * @property {Object.} patterns - Selected patterns and the reason. - */ - -/** - * @typedef {object} SoftwareConfig - * @propery {Object.} patterns - An object where the keys are the pattern names - * and the values whether to install them or not. - * @property {string|undefined} product - Product to install. - */ - -/** - * @typedef {Object} Pattern - * @property {string} name - Pattern name (internal ID). - * @property {string} category - Pattern category. - * @property {string} summary - User visible pattern name. - * @property {string} description - Long description of the pattern. - * @property {number} order - Display order (string!). - * @property {string} icon - Icon name (not path or file name!). - */ - /** * Software client * @@ -94,88 +58,6 @@ class ProductClient { constructor(client) { this.client = client; } - - /** - * Returns the registration of the selected product. - * - * @return {Promise} - */ - async getRegistration() { - const response = await this.client.get("/software/registration"); - if (!response.ok) { - console.log("Failed to get registration config:", response); - return { requirement: "unknown", code: null, email: null }; - } - const config = await response.json(); - - const { requirement, key: code, email } = config; - - const registration = { requirement, code, email }; - if (code.length === 0) registration.code = null; - if (email.length === 0) registration.email = null; - - return registration; - } - - /** - * Tries to register the selected product. - * - * @param {string} code - * @param {string} [email] - * @returns {Promise} - */ - async register(code, email = "") { - const response = await this.client.post("/software/registration", { key: code, email }); - if (response.status === 422) { - /** @type import('~/types/registration').RegistrationFailure */ - const body = await response.json(); - return { - success: false, - message: body.message, - }; - } - - return { - success: response.ok, // still we can fail 400 due to dbus issue or 500 if backend stop working. maybe some message for this case? - message: "", - }; - } - - /** - * Tries to deregister the selected product. - * - * @returns {Promise} - */ - async deregister() { - const response = await this.client.delete("/software/registration"); - - if (response.status === 422) { - /** @type import('~/types/registration').RegistrationFailure */ - const body = await response.json(); - return { - success: false, - message: body.message, - }; - } - - return { - success: response.ok, // still we can fail 400 due to dbus issue or 500 if backend stop working. maybe some message for this case? - message: "", - }; - } - - /** - * Registers a callback to run when the registration changes. - * - * @param {(registration: import('~/types/registration').Registration) => void} handler - Callback function. - */ - onRegistrationChange(handler) { - return this.client.ws().onEvent((event) => { - if (event.type === "RegistrationChanged" || event.type === "RegistrationRequirementChanged") { - this.getRegistration().then(handler); - } - }); - } } export { ProductClient, SoftwareClient }; diff --git a/web/src/client/software.test.js b/web/src/client/software.test.js deleted file mode 100644 index b3e68c2efb..0000000000 --- a/web/src/client/software.test.js +++ /dev/null @@ -1,182 +0,0 @@ -/* - * Copyright (c) [2022-2024] SUSE LLC - * - * All Rights Reserved. - * - * This program is free software; you can redistribute it and/or modify it - * under the terms of version 2 of the GNU General Public License as published - * by the Free Software Foundation. - * - * This program is distributed in the hope that it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or - * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for - * more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, contact SUSE LLC. - * - * To contact SUSE LLC about this file by physical or electronic mail, you may - * find current contact information at www.suse.com. - */ - -// @ts-check - -import DBusClient from "./dbus"; -import { HTTPClient } from "./http"; -import { ProductClient, SoftwareClient } from "./software"; - -const mockJsonFn = jest.fn(); - -const mockGetFn = jest.fn().mockImplementation(() => { - return { - ok: true, - json: mockJsonFn, - }; -}); - -const mockPostFn = jest.fn().mockImplementation(() => { - return { - ok: true, - }; -}); - -const mockDeleteFn = jest.fn().mockImplementation(() => { - return { - ok: true, - }; -}); - -jest.mock("./http", () => { - return { - HTTPClient: jest.fn().mockImplementation(() => { - return { - get: mockGetFn, - post: mockPostFn, - delete: mockDeleteFn, - }; - }), - }; -}); - -const PRODUCT_IFACE = "org.opensuse.Agama.Software1.Product"; -const REGISTRATION_IFACE = "org.opensuse.Agama1.Registration"; - -const tumbleweed = { - id: "Tumbleweed", - name: "openSUSE Tumbleweed", - description: "Tumbleweed is...", -}; - -const microos = { - id: "MicroOS", - name: "openSUSE MicroOS", - description: "MicroOS is...", -}; - -describe("ProductClient", () => { - describe("#getRegistration", () => { - describe("if the product is not registered yet", () => { - it("returns the expected registration result", async () => { - mockJsonFn.mockResolvedValue({ - key: "", - email: "", - requirement: "Optional", - }); - const http = new HTTPClient(new URL("http://localhost")); - const client = new ProductClient(http); - const registration = await client.getRegistration(); - expect(registration).toStrictEqual({ - code: null, - email: null, - requirement: "Optional", - }); - }); - }); - - describe("if the product is registered", () => { - it("returns the expected registration", async () => { - mockJsonFn.mockResolvedValue({ - key: "111222", - email: "test@test.com", - requirement: "Mandatory", - }); - const http = new HTTPClient(new URL("http://localhost")); - const client = new ProductClient(http); - const registration = await client.getRegistration(); - expect(registration).toStrictEqual({ - code: "111222", - email: "test@test.com", - requirement: "Mandatory", - }); - }); - }); - }); - - describe("#register", () => { - it("performs the backend call", async () => { - const http = new HTTPClient(new URL("http://localhost")); - const client = new ProductClient(http); - await client.register("111222", "test@test.com"); - expect(mockPostFn).toHaveBeenCalledWith("/software/registration", { - key: "111222", - email: "test@test.com", - }); - }); - - describe("when the action is correctly done", () => { - it("returns a successful result", async () => { - const http = new HTTPClient(new URL("http://localhost")); - const client = new ProductClient(http); - const result = await client.register("111222", "test@test.com"); - expect(result).toStrictEqual({ - success: true, - message: "", - }); - }); - }); - - describe("when the action fails", () => { - it("returns an unsuccessful result", async () => { - mockPostFn.mockImplementationOnce(() => { - return { ok: false }; - }); - const http = new HTTPClient(new URL("http://localhost")); - const client = new ProductClient(http); - const result = await client.register("111222", "test@test.com"); - expect(result).toStrictEqual({ - success: false, - message: "", - }); - }); - }); - }); - - describe("#deregister", () => { - describe("when the action is correctly done", () => { - it("returns a successful result", async () => { - const http = new HTTPClient(new URL("http://localhost")); - const client = new ProductClient(http); - const result = await client.deregister(); - expect(result).toStrictEqual({ - success: true, - message: "", - }); - }); - }); - - describe("when the action fails", () => { - it("returns an unsuccessful result", async () => { - mockDeleteFn.mockImplementationOnce(() => { - return { ok: false }; - }); - const http = new HTTPClient(new URL("http://localhost")); - const client = new ProductClient(http); - const result = await client.deregister(); - expect(result).toStrictEqual({ - success: false, - message: "", - }); - }); - }); - }); -}); diff --git a/web/src/components/product/ProductRegistrationForm.test.jsx b/web/src/components/product/ProductRegistrationForm.test.jsx deleted file mode 100644 index 2c4a9ea44e..0000000000 --- a/web/src/components/product/ProductRegistrationForm.test.jsx +++ /dev/null @@ -1,95 +0,0 @@ -/* - * Copyright (c) [2023] SUSE LLC - * - * All Rights Reserved. - * - * This program is free software; you can redistribute it and/or modify it - * under the terms of version 2 of the GNU General Public License as published - * by the Free Software Foundation. - * - * This program is distributed in the hope that it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or - * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for - * more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, contact SUSE LLC. - * - * To contact SUSE LLC about this file by physical or electronic mail, you may - * find current contact information at www.suse.com. - */ - -import React, { useState } from "react"; -import { Button } from "@patternfly/react-core"; -import { screen } from "@testing-library/react"; -import { plainRender } from "~/test-utils"; -import { ProductRegistrationForm } from "~/components/product"; - -it.skip("renders a field for entering the registration code", async () => { - plainRender(); - await screen.findByLabelText(/Registration code/); -}); - -it.skip("renders a field for entering an email", async () => { - plainRender(); - await screen.findByLabelText("Email"); -}); - -const ProductRegistrationFormTest = () => { - const [isSubmitted, setIsSubmitted] = useState(false); - const [isValid, setIsValid] = useState(true); - - return ( - <> - - - {isSubmitted &&

Form is submitted!

} - {isValid === false &&

Form is not valid!

} - - ); -}; - -it.skip("triggers the onSubmit callback", async () => { - const { user } = plainRender(); - - expect(screen.queryByText("Form is submitted!")).toBeNull(); - - const button = screen.getByRole("button", { name: "Accept" }); - await user.click(button); - await screen.findByText("Form is submitted!"); -}); - -it.skip("sets the form as invalid if there is no code", async () => { - plainRender(); - await screen.findByText("Form is not valid!"); -}); - -it.skip("sets the form as invalid if there is a code and a wrong email", async () => { - const { user } = plainRender(); - const codeInput = await screen.findByLabelText(/Registration code/); - const emailInput = await screen.findByLabelText("Email"); - await user.type(codeInput, "111222"); - await user.type(emailInput, "foo"); - - await screen.findByText("Form is not valid!"); -}); - -it.skip("does not set the form as invalid if there is a code and no email", async () => { - const { user } = plainRender(); - const codeInput = await screen.findByLabelText(/Registration code/); - await user.type(codeInput, "111222"); - - expect(screen.queryByText("Form is not valid!")).toBeNull(); -}); - -it.skip("does not set the form as invalid if there is a code and a correct email", async () => { - const { user } = plainRender(); - const codeInput = await screen.findByLabelText(/Registration code/); - const emailInput = await screen.findByLabelText("Email"); - await user.type(codeInput, "111222"); - await user.type(emailInput, "test@test.com"); - - expect(screen.queryByText("Form is not valid!")).toBeNull(); -}); diff --git a/web/src/components/product/ProductRegistrationPage.jsx b/web/src/components/product/ProductRegistrationPage.jsx deleted file mode 100644 index df3672003b..0000000000 --- a/web/src/components/product/ProductRegistrationPage.jsx +++ /dev/null @@ -1,90 +0,0 @@ -/* - * Copyright (c) [2023] SUSE LLC - * - * All Rights Reserved. - * - * This program is free software; you can redistribute it and/or modify it - * under the terms of version 2 of the GNU General Public License as published - * by the Free Software Foundation. - * - * This program is distributed in the hope that it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or - * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for - * more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, contact SUSE LLC. - * - * To contact SUSE LLC about this file by physical or electronic mail, you may - * find current contact information at www.suse.com. - */ - -import React, { useState } from "react"; -import { Alert, Form, FormGroup } from "@patternfly/react-core"; -import { useNavigate } from "react-router-dom"; -import { EmailInput, Page, PasswordInput } from "~/components/core"; -import { useProduct } from "~/queries/software"; -import { useInstallerClient } from "~/context/installer"; -import { _ } from "~/i18n"; -import { sprintf } from "sprintf-js"; - -/** - * Form for registering a product. - * @component - * - * @param {object} props - */ -export default function ProductRegistrationPage() { - const navigate = useNavigate(); - const { software } = useInstallerClient(); - const { selectedProduct } = useProduct(); - const [code, setCode] = useState(""); - const [email, setEmail] = useState(""); - const [error, setError] = useState(); - - // FIXME: re-introduce validations and "isLoading" status - // TODO: see if would be better to use https://reactrouter.com/en/main/components/form - - const onCancel = () => { - setError(null); - navigate(".."); - }; - - const onSubmit = async (e) => { - e.preventDefault(); - const result = await software.product.register(code, email); - if (result.success) { - software.probe(); - } else { - setError(result.message); - } - }; - - return ( - <> - -

{sprintf(_("Register %s"), selectedProduct.name)}

- {error && ( - -

{error}

-
- )} -
- - setCode(v)} /> - - - setEmail(v)} /> - -
-
- - - - - {_("Accept")} - - - - ); -} diff --git a/web/src/components/product/index.js b/web/src/components/product/index.js index c6153cccaa..c22d43e64a 100644 --- a/web/src/components/product/index.js +++ b/web/src/components/product/index.js @@ -19,6 +19,5 @@ * find current contact information at www.suse.com. */ -export { default as ProductRegistrationPage } from "./ProductRegistrationPage"; export { default as ProductSelectionPage } from "./ProductSelectionPage"; export { default as ProductSelectionProgress } from "./ProductSelectionProgress";