From 9a69f5251cac8e6352f896bc0e5efe6aac1650f0 Mon Sep 17 00:00:00 2001 From: DilshanSenarath <74205483+DilshanSenarath@users.noreply.github.com> Date: Wed, 17 Jul 2024 10:06:17 +0530 Subject: [PATCH] add draft unit test for sso templates feature --- .../__mocks__/application-template.ts | 577 +++++++++ .../__tests__/__mocks__/application.ts | 324 +++++ .../mock-application-templates-component.tsx | 77 ++ ...ock-dynamic-field-validation-component.tsx | 79 ++ .../components/application-list.test.tsx | 63 + .../application-creation-adapter.test.tsx | 73 ++ .../application-template-card.test.tsx | 67 + .../application-template-grid.test.tsx | 176 +++ .../application-create-wizard.test.tsx | 182 +++ .../application-edit-form.test.tsx | 128 ++ .../help-panel/markdown-guide.test.tsx | 43 + .../use-dynamic-field-validation.test.tsx | 125 ++ .../__tests__/pages/application-edit.test.tsx | 193 +++ .../pages/application-template.test.tsx | 139 +++ .../application-templates-provider.test.tsx | 56 + ...lication-template-management-utils.test.ts | 71 ++ .../build-callback-urls-with-regexp.test.ts | 43 + .../admin.extensions.v1/configs/common.tsx | 1 + .../configs/models/common.ts | 3 +- .../test-configs/__mocks__/window.ts | 1080 +++++++++-------- features/jest.config.ts | 3 +- modules/unit-testing/__mocks__/global.ts | 2 +- .../__mocks__/redux/redux-store-state.ts | 244 +++- .../__mocks__/window/app-utils.ts | 17 + modules/unit-testing/utils.tsx | 35 +- 25 files changed, 3245 insertions(+), 556 deletions(-) create mode 100644 features/admin.applications.v1/__tests__/__mocks__/application-template.ts create mode 100644 features/admin.applications.v1/__tests__/__mocks__/application.ts create mode 100644 features/admin.applications.v1/__tests__/__mocks__/mock-application-templates-component.tsx create mode 100644 features/admin.applications.v1/__tests__/__mocks__/mock-dynamic-field-validation-component.tsx create mode 100644 features/admin.applications.v1/__tests__/components/application-list.test.tsx create mode 100644 features/admin.applications.v1/__tests__/components/application-templates/application-creation-adapter.test.tsx create mode 100644 features/admin.applications.v1/__tests__/components/application-templates/application-template-card.test.tsx create mode 100644 features/admin.applications.v1/__tests__/components/application-templates/application-template-grid.test.tsx create mode 100644 features/admin.applications.v1/__tests__/components/dynamic-forms/application-create-wizard.test.tsx create mode 100644 features/admin.applications.v1/__tests__/components/dynamic-forms/application-edit-form.test.tsx create mode 100644 features/admin.applications.v1/__tests__/components/help-panel/markdown-guide.test.tsx create mode 100644 features/admin.applications.v1/__tests__/hooks/use-dynamic-field-validation.test.tsx create mode 100644 features/admin.applications.v1/__tests__/pages/application-edit.test.tsx create mode 100644 features/admin.applications.v1/__tests__/pages/application-template.test.tsx create mode 100644 features/admin.applications.v1/__tests__/provider/application-templates-provider.test.tsx create mode 100644 features/admin.applications.v1/__tests__/utils/application-template-management-utils.test.ts create mode 100644 features/admin.applications.v1/__tests__/utils/build-callback-urls-with-regexp.test.ts diff --git a/features/admin.applications.v1/__tests__/__mocks__/application-template.ts b/features/admin.applications.v1/__tests__/__mocks__/application-template.ts new file mode 100644 index 00000000000..2f3fc4f807e --- /dev/null +++ b/features/admin.applications.v1/__tests__/__mocks__/application-template.ts @@ -0,0 +1,577 @@ +/** + * Copyright (c) 2024, WSO2 LLC. (https://www.wso2.com). + * + * WSO2 LLC. licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { + ApplicationEditTabContentTypes, + ApplicationEditTabMetadataInterface, + ApplicationTemplateCategories, + ApplicationTemplateInterface, + ApplicationTemplateListInterface, + ApplicationTemplateMetadataInterface, + CategorizedApplicationTemplatesInterface +} from "../../models/application-templates"; +import { DynamicFieldInterface, DynamicInputFieldTypes, ValidationRuleTypes } from "../../models/dynamic-fields"; + +export const applicationTemplatesListMockResponse: ApplicationTemplateListInterface[] = [ + { + category: ApplicationTemplateCategories.DEFAULT, + customAttributes: [ + { + key: "supportedTechnologies", + value: [ + { + displayName: "React", + logo: "{{CONSOLE_BASE_URL}}/resources/applications/assets/images/technologies/react-logo.svg" + }, + { + displayName: "Angular", + logo: "{{CONSOLE_BASE_URL}}/resources/applications/assets/images/technologies/angular-logo.svg" + }, + { + displayName: "Vue", + logo: "{{CONSOLE_BASE_URL}}/resources/applications/assets/images/technologies/vue-logo.svg" + }, + { + displayName: "Javascript", + logo: "{{CONSOLE_BASE_URL}}/resources/applications/assets/images/technologies/" + + "javascript-logo.svg" + } + ] + } + ], + description: "A web application that runs application logic in the browser.", + displayOrder: 0, + id: "single-page-application", + image: "{{CONSOLE_BASE_URL}}/resources/applications/assets/images/illustrations/spa-template.svg", + name: "Single-Page Application", + self: "/api/server/v1/extensions/applications/single-page-application", + tags: [ + "Default", + "OIDC" + ], + type: "applications" + }, + { + category: ApplicationTemplateCategories.DEFAULT, + customAttributes: [ + { + key: "supportedTechnologies", + value: [ + { + displayName: "Java EE", + logo: "{{CONSOLE_BASE_URL}}/resources/applications/assets/images/technologies/java-logo.svg" + }, + { + displayName: ".NET", + logo: "{{CONSOLE_BASE_URL}}/resources/applications/assets/images/technologies/dotnet-logo.svg" + }, + { + displayName: "Node.js", + logo: "{{CONSOLE_BASE_URL}}/resources/applications/assets/images/technologies/nodejs-logo.svg" + }, + { + displayName: "PHP", + logo: "{{CONSOLE_BASE_URL}}/resources/applications/assets/images/technologies/php-logo.svg" + } + ] + } + ], + description: "A web application that runs application logic on the server.", + displayOrder: 1, + id: "traditional-web-application", + image: "{{CONSOLE_BASE_URL}}/resources/applications/assets/images/illustrations/traditional-template.svg", + name: "Traditional Web Application", + self: "/api/server/v1/extensions/applications/traditional-web-application", + tags: [ + "Default", + "OIDC", + "SAML" + ], + type: "applications" + }, + { + category: ApplicationTemplateCategories.DEFAULT, + customAttributes: [ + { + key: "supportedTechnologies", + value: [ + { + displayName: "OIDC", + logo: "{{CONSOLE_BASE_URL}}/resources/applications/assets/images/technologies/" + + "openid-connect.png" + }, + { + displayName: "SAML", + logo: "{{CONSOLE_BASE_URL}}/resources/applications/assets/images/technologies/saml.png" + }, + { + displayName: "WS-Federation", + logo: "{{CONSOLE_BASE_URL}}/resources/applications/assets/images/technologies/ws-fed.png" + } + ] + } + ], + description: "Applications built using standard protocols.", + displayOrder: 3, + id: "custom-application", + image: "{{CONSOLE_BASE_URL}}/resources/applications/assets/images/illustrations/standard-based-template.svg", + name: "Standard-Based Application", + self: "/api/server/v1/extensions/applications/custom-application", + tags: [ + "Default", + "OIDC", + "SAML", + "WS-Federation" + ], + type: "applications" + }, + { + category: ApplicationTemplateCategories.SSO_INTEGRATION, + customAttributes: [ + { + key: "comingSoon", + value: "false" + } + ], + description: "Customer relationship management (CRM) platform that enables businesses to manage their sales" + + ", marketing, and customer service operations efficiently.", + displayOrder: 7, + id: "salesforce", + image: "{{CONSOLE_BASE_URL}}/resources/applications/assets/images/illustrations/salesforce.png", + name: "Salesforce", + self: "/api/server/v1/extensions/applications/salesforce", + tags: [ + "SAML", + "SSO" + ], + type: "applications" + } +]; + +export const categorizedApplicationTemplatesListMockResponse: CategorizedApplicationTemplatesInterface[] = [ + { + description: "applications:templates.categories.default.description", + displayName: "applications:templates.categories.default.displayName", + displayOrder: 0, + id: ApplicationTemplateCategories.DEFAULT, + templates: [ + { + category: ApplicationTemplateCategories.DEFAULT, + customAttributes: [ + { + key: "supportedTechnologies", + value: [ + { + displayName: "React", + logo: "{{CONSOLE_BASE_URL}}/resources/applications/assets/images/" + + "technologies/react-logo.svg" + }, + { + displayName: "Angular", + logo: "{{CONSOLE_BASE_URL}}/resources/applications/assets/images/" + + "technologies/angular-logo.svg" + }, + { + displayName: "Vue", + logo: "{{CONSOLE_BASE_URL}}/resources/applications/assets/images/" + + "technologies/vue-logo.svg" + }, + { + displayName: "Javascript", + logo: "{{CONSOLE_BASE_URL}}/resources/applications/assets/images/technologies/" + + "javascript-logo.svg" + } + ] + } + ], + description: "A web application that runs application logic in the browser.", + displayOrder: 0, + id: "single-page-application", + image: "{{CONSOLE_BASE_URL}}/resources/applications/assets/images/illustrations/spa-template.svg", + name: "Single-Page Application", + self: "/api/server/v1/extensions/applications/single-page-application", + tags: [ + "Default", + "OIDC" + ], + type: "applications" + }, + { + category: ApplicationTemplateCategories.DEFAULT, + customAttributes: [ + { + key: "supportedTechnologies", + value: [ + { + displayName: "Java EE", + logo: "{{CONSOLE_BASE_URL}}/resources/applications/assets/images/" + + "technologies/java-logo.svg" + }, + { + displayName: ".NET", + logo: "{{CONSOLE_BASE_URL}}/resources/applications/assets/images/" + + "technologies/dotnet-logo.svg" + }, + { + displayName: "Node.js", + logo: "{{CONSOLE_BASE_URL}}/resources/applications/assets/images/" + + "technologies/nodejs-logo.svg" + }, + { + displayName: "PHP", + logo: "{{CONSOLE_BASE_URL}}/resources/applications/assets/images/" + + "technologies/php-logo.svg" + } + ] + } + ], + description: "A web application that runs application logic on the server.", + displayOrder: 1, + id: "traditional-web-application", + image: "{{CONSOLE_BASE_URL}}/resources/applications/assets/images/" + + "illustrations/traditional-template.svg", + name: "Traditional Web Application", + self: "/api/server/v1/extensions/applications/traditional-web-application", + tags: [ + "Default", + "OIDC", + "SAML" + ], + type: "applications" + }, + { + category: ApplicationTemplateCategories.DEFAULT, + customAttributes: [ + { + key: "supportedTechnologies", + value: [ + { + displayName: "OIDC", + logo: "{{CONSOLE_BASE_URL}}/resources/applications/assets/images/technologies/" + + "openid-connect.png" + }, + { + displayName: "SAML", + logo: "{{CONSOLE_BASE_URL}}/resources/applications/assets/images/technologies/saml.png" + }, + { + displayName: "WS-Federation", + logo: "{{CONSOLE_BASE_URL}}/resources/applications/assets/images/" + + "technologies/ws-fed.png" + } + ] + } + ], + description: "Applications built using standard protocols.", + displayOrder: 3, + id: "custom-application", + image: "{{CONSOLE_BASE_URL}}/resources/applications/assets/images/" + + "illustrations/standard-based-template.svg", + name: "Standard-Based Application", + self: "/api/server/v1/extensions/applications/custom-application", + tags: [ + "Default", + "OIDC", + "SAML", + "WS-Federation" + ], + type: "applications" + } + ] + }, + { + description: "applications:templates.categories.ssoIntegration.description", + displayName: "applications:templates.categories.ssoIntegration.displayName", + displayOrder: 1, + id: ApplicationTemplateCategories.SSO_INTEGRATION, + templates: [ + { + category: ApplicationTemplateCategories.SSO_INTEGRATION, + customAttributes: [ + { + key: "comingSoon", + value: "false" + } + ], + description: "Customer relationship management (CRM) platform that enables businesses " + + "to manage their sales, marketing, and customer service operations efficiently.", + displayOrder: 7, + id: "salesforce", + image: "{{CONSOLE_BASE_URL}}/resources/applications/assets/images/illustrations/salesforce.png", + name: "Salesforce", + self: "/api/server/v1/extensions/applications/salesforce", + tags: [ + "SAML", + "SSO" + ], + type: "applications" + } + ] + }, + { + description: "applications:templates.categories.other.description", + displayName: "applications:templates.categories.other.displayName", + displayOrder: Infinity, + id: "OTHER", + templates: [] + } +]; + +export const TEMPLATE_NAMES: { [key: string]: string } = { + salesforce: "Salesforce", + spa: "Single-Page Application" +}; + +export const applicationTemplateMockResponse: ApplicationTemplateInterface = { + category: ApplicationTemplateCategories.SSO_INTEGRATION, + description: "Salesforce is a customer relationship management (CRM) platform that enables " + + "businesses to manage their sales, marketing, and customer service operations efficiently.", + displayOrder: 6, + id: "salesforce", + image: "{{CONSOLE_BASE_URL}}/resources/applications/assets/images/illustrations/salesforce.svg", + name: "Salesforce", + payload: { + advancedConfigurations: { + discoverableByEndUsers: false, + skipLogoutConsent: true + }, + authenticationSequence: { + steps: [ + { + id: 1, + options: [ + { + authenticator: "basic", + idp: "LOCAL" + } + ] + } + ], + type: "DEFAULT" + }, + claimConfiguration: { + dialect: "LOCAL", + requestedClaims: [ + { + claim: { + uri: "http://wso2.org/claims/username" + }, + mandatory: false + } + ] + }, + inboundProtocolConfiguration: { + oidc: { + accessToken: { + applicationAccessTokenExpiryInSeconds: 3600, + bindingType: "sso-session", + revokeTokensWhenIDPSessionTerminated: true, + type: "Default", + userAccessTokenExpiryInSeconds: 3600, + validateTokenBinding: false + }, + allowedOrigins: [ + "https://example.com" + ], + callbackURLs: [ + "https://example.com/login" + ], + grantTypes: [ + "authorization_code", + "refresh_token" + ], + pkce: { + mandatory: true, + supportPlainTransformAlgorithm: false + }, + publicClient: true, + refreshToken: { + expiryInSeconds: 86400, + renewRefreshToken: true + } + } + }, + name: "Salesforce", + templateId: "salesforce" + }, + tags: [ + "SAML", + "SSO" + ], + type: "applications" +}; + +export const applicationTemplateMetadataMockResponse: ApplicationTemplateMetadataInterface = { + create: { + form: { + fields: [ + { + "aria-label": "Application Name", + dataComponentId: "salesforce-create-wizard-application-name", + id: "application-name", + label: "Name", + name: "name", + placeholder: "My App", + required: true, + type: DynamicInputFieldTypes.TEXT, + validations: [ + { + type: ValidationRuleTypes.APPLICATION_NAME + } + ] + } + ], + submitDefinedFieldsOnly: true + }, + guide: [ + "#### Name\nA unique name to identify your application.\n\nFor example, " + + "My App\n\n---\n\n#### Authorized Redirect URLs\nThe URL to which the " + + "authorization code is sent upon authentication and where the user is " + + "redirected upon logout.\n\nFor example, [https://myapp.io/login](https" + + "://myapp.io/login)\n\n---\n\n#### Allow sharing with sub-organizations" + + "\n\nIf enabled, it will share this application with all or any selected " + + "sub-organizations that belong to your root organization." + ], + isApplicationSharable: true + }, + edit: { + defaultActiveTabId: "salesforce-settings", + tabs: [ + { + contentType: ApplicationEditTabContentTypes.GUIDE, + displayName: "Quick Start", + guide: "# Dillinger\n## _The Last Markdown Editor", + id: "quick-start" + }, + { + id: "general" + }, + { + displayName: "Application Roles", + id: "application-roles" + }, + { + displayName: "Attributes", + id: "user-attributes" + }, + { + contentType: ApplicationEditTabContentTypes.FORM, + displayName: "Salesforce Settings", + form: { + fields: [ + { + "aria-label": "Application Name", + dataComponentId: "dynamic-application-edit-form-application-name", + id: "application-name", + label: "Name", + name: "name", + placeholder: "My App", + required: true, + type: DynamicInputFieldTypes.TEXT, + validations: [ + { + type: ValidationRuleTypes.APPLICATION_NAME + } + ] + }, + { + "aria-label": "application-description", + dataComponentId: "dynamic-application-edit-form-application-description", + id: "application-description", + label: "Description", + name: "description", + placeholder: "My App Description", + required: false, + type: DynamicInputFieldTypes.TEXTAREA + } + ], + submitDefinedFieldsOnly: true + }, + id: "salesforce-settings" + } + ] + } +}; + +export const dynamicApplicationEditTabMetadataMockObject: ApplicationEditTabMetadataInterface = { + contentType: ApplicationEditTabContentTypes.FORM, + displayName: "Salesforce Settings", + form: { + fields: [ + { + "aria-label": "Application Name", + dataComponentId: "dynamic-application-edit-form-application-name", + id: "application-name", + label: "Name", + name: "name", + placeholder: "My App", + required: true, + type: DynamicInputFieldTypes.TEXT, + validations: [ + { + type: ValidationRuleTypes.APPLICATION_NAME + } + ] + }, + { + "aria-label": "application-description", + dataComponentId: "dynamic-application-edit-form-application-description", + id: "application-description", + label: "Description", + name: "description", + placeholder: "My App Description", + required: false, + type: DynamicInputFieldTypes.TEXTAREA + } + ], + submitDefinedFieldsOnly: true + }, + id: "salesforce-settings" +}; + +export const applicationNameDynamicFormFieldMock: DynamicFieldInterface = { + "aria-label": "Application Name", + dataComponentId: "application-name", + id: "application-name", + label: "Name", + name: "name", + placeholder: "My App", + required: true, + type: DynamicInputFieldTypes.TEXT, + validations: [ + { + type: ValidationRuleTypes.APPLICATION_NAME + } + ] +}; + +export const domainNameDynamicFormFieldMock: DynamicFieldInterface = { + "aria-label": "Domain Name", + dataComponentId: "domain-name", + id: "domain-name", + label: "Domain Name", + name: "advancedConfigurations.additionalSpProperties.[0].value", + placeholder: "https://example.com", + required: true, + type: DynamicInputFieldTypes.TEXT, + validations: [ + { + type: ValidationRuleTypes.DOMAIN_NAME + } + ] +}; diff --git a/features/admin.applications.v1/__tests__/__mocks__/application.ts b/features/admin.applications.v1/__tests__/__mocks__/application.ts new file mode 100644 index 00000000000..f68ffe9db62 --- /dev/null +++ b/features/admin.applications.v1/__tests__/__mocks__/application.ts @@ -0,0 +1,324 @@ +/** + * Copyright (c) 2024, WSO2 LLC. (https://www.wso2.com). + * + * WSO2 LLC. licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { RoleAudienceTypes } from "@wso2is/admin.roles.v2/constants"; +import { ApplicationAccessTypes, ApplicationInterface, ApplicationListInterface } from "../../models"; + +export const applicationListMockResponse: ApplicationListInterface = { + applications: [ + { + access: ApplicationAccessTypes.WRITE, + advancedConfigurations: { + additionalSpProperties: [ + { + displayName: "Is B2B Self Service Application", + name: "isB2BSelfServiceApp", + value: "false" + } + ], + attestationMetaData: { + enableClientAttestation: false + }, + discoverableByEndUsers: false, + enableAPIBasedAuthentication: false, + enableAuthorization: false, + fragment: false, + returnAuthenticatedIdpList: false, + saas: false, + skipLoginConsent: false, + skipLogoutConsent: true + }, + clientId: "kcn1kuV1yiZbaDofF2Xfg4jUpsUa", + id: "142fbbc0-69b8-4f3d-a647-adecc9f29804", + issuer: "", + name: "Sample salesforce SSO app", + realm: "", + self: "/api/server/v1/applications/142fbbc0-69b8-4f3d-a647-adecc9f29804", + templateId: "salesforce" + }, + { + access: ApplicationAccessTypes.WRITE, + advancedConfigurations: { + additionalSpProperties: [ + { + displayName: "Is B2B Self Service Application", + name: "isB2BSelfServiceApp", + value: "false" + } + ], + attestationMetaData: { + enableClientAttestation: false + }, + discoverableByEndUsers: false, + enableAPIBasedAuthentication: false, + enableAuthorization: false, + fragment: false, + returnAuthenticatedIdpList: false, + saas: false, + skipLoginConsent: true, + skipLogoutConsent: true + }, + clientId: "Y4LmFp5bwqvylijALgOCYPGvlTMa", + id: "c3a3d3ce-4166-416a-963a-94e1c1c8de5f", + issuer: "", + name: "SPA", + realm: "", + self: "/api/server/v1/applications/c3a3d3ce-4166-416a-963a-94e1c1c8de5f", + templateId: "6a90e4b0-fbff-42d7-bfde-1efd98f07cd7" + } + ], + count: 2, + links: [], + startIndex: 1, + totalResults: 2 +}; + +export const applicationSearchListMockResponse: ApplicationListInterface = { + applications: [ + { + access: ApplicationAccessTypes.WRITE, + advancedConfigurations: { + additionalSpProperties: [ + { + displayName: "Is B2B Self Service Application", + name: "isB2BSelfServiceApp", + value: "false" + } + ], + attestationMetaData: { + enableClientAttestation: false + }, + discoverableByEndUsers: false, + enableAPIBasedAuthentication: false, + enableAuthorization: false, + fragment: false, + returnAuthenticatedIdpList: false, + saas: false, + skipLoginConsent: false, + skipLogoutConsent: true + }, + clientId: "kcn1kuV1yiZbaDofF2Xfg4jUpsUa", + id: "142fbbc0-69b8-4f3d-a647-adecc9f29804", + issuer: "", + name: "Salesforce", + realm: "", + self: "/api/server/v1/applications/142fbbc0-69b8-4f3d-a647-adecc9f29804", + templateId: "salesforce" + } + ], + count: 1, + links: [], + startIndex: 1, + totalResults: 1 +}; + +export const applicationMockResponse: ApplicationInterface = { + access: ApplicationAccessTypes.WRITE, + advancedConfigurations: { + additionalSpProperties: [ + { + displayName: "Is B2B Self Service Application", + name: "isB2BSelfServiceApp", + value: "false" + } + ], + attestationMetaData: { + androidPackageName: "", + appleAppId: "", + enableClientAttestation: false + }, + discoverableByEndUsers: false, + enableAPIBasedAuthentication: false, + enableAuthorization: false, + fragment: false, + returnAuthenticatedIdpList: false, + saas: false, + skipLoginConsent: false, + skipLogoutConsent: true + }, + associatedRoles: { + allowedAudience: RoleAudienceTypes.ORGANIZATION, + roles: [] + }, + authenticationSequence: { + attributeStepId: 1, + requestPathAuthenticators: [], + steps: [ + { + id: 1, + options: [ + { + authenticator: "BasicAuthenticator", + idp: "LOCAL" + } + ] + } + ], + subjectStepId: 1, + type: "DEFAULT" + }, + claimConfiguration: { + claimMappings: [ + { + applicationClaim: "http://wso2.org/claims/username", + localClaim: { + uri: "http://wso2.org/claims/username" + } + } + ], + dialect: "LOCAL", + requestedClaims: [ + { + claim: { + uri: "http://wso2.org/claims/username" + }, + mandatory: false + } + ], + role: { + claim: { + uri: "http://wso2.org/claims/role" + }, + includeUserDomain: true, + mappings: [] + }, + subject: { + claim: { + uri: "http://wso2.org/claims/userid" + }, + includeTenantDomain: false, + includeUserDomain: false, + mappedLocalSubjectMandatory: false, + useMappedLocalSubject: false + } + }, + clientId: "kcn1kuV1yfZbaDofF2Xfg4jUpsUa", + id: "142fbbc0-69b8-4f3d-a6a7-adecc9f29804", + inboundProtocols: [ + { + self: "/api/server/v1/applications/142fbbc0-69b8-4f3d-a6a7-adecc9f29804/inbound-protocols/oidc", + type: "oauth2" + } + ], + isManagementApp: false, + issuer: "", + name: "Salesforce", + provisioningConfigurations: { + outboundProvisioningIdps: [] + }, + realm: "", + templateId: "salesforce" +}; + +export const spaApplicationMockResponse: ApplicationInterface = { + access: ApplicationAccessTypes.WRITE, + advancedConfigurations: { + additionalSpProperties: [ + { + displayName: "Is B2B Self Service Application", + name: "isB2BSelfServiceApp", + value: "false" + } + ], + attestationMetaData: { + androidPackageName: "", + appleAppId: "", + enableClientAttestation: false + }, + discoverableByEndUsers: false, + enableAPIBasedAuthentication: false, + enableAuthorization: false, + fragment: false, + returnAuthenticatedIdpList: false, + saas: false, + skipLoginConsent: true, + skipLogoutConsent: true + }, + associatedRoles: { + allowedAudience: RoleAudienceTypes.ORGANIZATION, + roles: [] + }, + authenticationSequence: { + attributeStepId: 1, + requestPathAuthenticators: [], + steps: [ + { + id: 1, + options: [ + { + authenticator: "BasicAuthenticator", + idp: "LOCAL" + } + ] + } + ], + subjectStepId: 1, + type: "DEFAULT" + }, + claimConfiguration: { + claimMappings: [ + { + applicationClaim: "http://wso2.org/claims/username", + localClaim: { + uri: "http://wso2.org/claims/username" + } + } + ], + dialect: "LOCAL", + requestedClaims: [ + { + claim: { + uri: "http://wso2.org/claims/username" + }, + mandatory: false + } + ], + role: { + claim: { + uri: "http://wso2.org/claims/role" + }, + includeUserDomain: true, + mappings: [] + }, + subject: { + claim: { + uri: "http://wso2.org/claims/userid" + }, + includeTenantDomain: false, + includeUserDomain: false, + mappedLocalSubjectMandatory: false, + useMappedLocalSubject: false + } + }, + clientId: "Y4LmFp5bwqvylgjALgOCYPGvlTMa", + id: "c3a3d3ce-4166-4e6a-963a-94e1c1c8de5f", + inboundProtocols: [ + { + self: "/api/server/v1/applications/c3a3d3ce-4166-4e6a-963a-94e1c1c8de5f/inbound-protocols/oidc", + type: "oauth2" + } + ], + isManagementApp: false, + issuer: "", + name: "SPA", + provisioningConfigurations: { + outboundProvisioningIdps: [] + }, + realm: "", + templateId: "6a90e4b0-fbff-42d7-bfde-1efd98f07cd7" +}; diff --git a/features/admin.applications.v1/__tests__/__mocks__/mock-application-templates-component.tsx b/features/admin.applications.v1/__tests__/__mocks__/mock-application-templates-component.tsx new file mode 100644 index 00000000000..b687df396f0 --- /dev/null +++ b/features/admin.applications.v1/__tests__/__mocks__/mock-application-templates-component.tsx @@ -0,0 +1,77 @@ +/** + * Copyright (c) 2024, WSO2 LLC. (https://www.wso2.com). + * + * WSO2 LLC. licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { IdentifiableComponentInterface } from "@wso2is/core/models"; +import React, { FunctionComponent, PropsWithChildren } from "react"; +import useApplicationTemplates from "../../hooks/use-application-templates"; +import { CategorizedApplicationTemplatesInterface } from "../../models/application-templates"; + +/** + * Props interface for the `MockApplicationTemplatesComponent`. + */ +export type MockApplicationTemplatesComponentProps = IdentifiableComponentInterface; + +/** + * Mock component that uses the application templates hook. + * + * @param props - Props for the `MockApplicationTemplatesComponent`. + * @returns MockApplicationTemplatesComponent + */ +const MockApplicationTemplatesComponent: FunctionComponent< + PropsWithChildren +> = ( + props: PropsWithChildren +) => { + const { + "data-componentid": componentId + } = props; + + const { + categorizedTemplates, + isApplicationTemplatesRequestLoading, + templates + } = useApplicationTemplates(); + + return ( + <> + { + isApplicationTemplatesRequestLoading !== undefined + ?
+ : null + } + { + categorizedTemplates.map((template: CategorizedApplicationTemplatesInterface) => ( +
+ { `Number of templates-${template?.templates?.length}` } +
+ )) + } + { +
+ { `Number of templates-${templates?.length}` } +
+ } + + ); +}; + +MockApplicationTemplatesComponent.defaultProps = { + "data-componentid": "mock-application-templates-component" +}; + +export default MockApplicationTemplatesComponent; diff --git a/features/admin.applications.v1/__tests__/__mocks__/mock-dynamic-field-validation-component.tsx b/features/admin.applications.v1/__tests__/__mocks__/mock-dynamic-field-validation-component.tsx new file mode 100644 index 00000000000..88851f55c68 --- /dev/null +++ b/features/admin.applications.v1/__tests__/__mocks__/mock-dynamic-field-validation-component.tsx @@ -0,0 +1,79 @@ +/** + * Copyright (c) 2024, WSO2 LLC. (https://www.wso2.com). + * + * WSO2 LLC. licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { IdentifiableComponentInterface } from "@wso2is/core/models"; +import get from "lodash-es/get"; +import React, { FunctionComponent, PropsWithChildren, useMemo, useState } from "react"; +import useDynamicFieldValidations from "../../hooks/use-dynamic-field-validation"; +import { MainApplicationInterface } from "../../models"; +import { DynamicFieldInterface } from "../../models/dynamic-fields"; + +/** + * Props interface for the `MockDynamicFieldValidationComponent`. + */ +export interface MockDynamicFieldValidationComponentProps extends IdentifiableComponentInterface { + /** + * Values to be validated. + */ + formValues: MainApplicationInterface; + /** + * Metadata of the dynamic field. + */ + field: DynamicFieldInterface; +} + +/** + * Mock component that uses the dynamic field validation. + * + * @param props - Props for the `MockDynamicFieldValidationComponent`. + * @returns MockDynamicFieldValidationComponent + */ +const MockDynamicFieldValidationComponent: FunctionComponent< + PropsWithChildren +> = ( + props: PropsWithChildren +) => { + const { + field, + formValues, + "data-componentid": componentId + } = props; + + const [ displayMessage, setDisplayMessage ] = useState(""); + + const { validate } = useDynamicFieldValidations(); + + useMemo(() => { + validate(formValues, [ field ]) + .then((errors: { [key in keyof Partial]: string }) => { + const errorMsg: string = get(errors, field?.name); + + if (errorMsg) { + setDisplayMessage(errorMsg); + } + }); + }, [ formValues, field, validate ]); + + return

{ displayMessage }

; +}; + +MockDynamicFieldValidationComponent.defaultProps = { + "data-componentid": "mock-dynamic-field-validation-component" +}; + +export default MockDynamicFieldValidationComponent; diff --git a/features/admin.applications.v1/__tests__/components/application-list.test.tsx b/features/admin.applications.v1/__tests__/components/application-list.test.tsx new file mode 100644 index 00000000000..fe9b9c0ad85 --- /dev/null +++ b/features/admin.applications.v1/__tests__/components/application-list.test.tsx @@ -0,0 +1,63 @@ +/** + * Copyright (c) 2024, WSO2 LLC. (https://www.wso2.com). + * + * WSO2 LLC. licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import * as Grid from "@oxygen-ui/react/Grid"; +import React, { PropsWithChildren } from "react"; +import { render, screen, waitFor } from "../../../test-configs"; +import * as useGetApplicationTemplates from "../../api/use-get-application-templates"; +import { ApplicationList, ApplicationListPropsInterface } from "../../components/application-list"; +import ApplicationTemplatesProvider from "../../provider/application-templates-provider"; +import { applicationListMockResponse } from "../__mocks__/application"; +import { + TEMPLATE_NAMES, + applicationTemplatesListMockResponse +} from "../__mocks__/application-template"; +import "@testing-library/jest-dom"; + +describe("[Applications Management Feature] - ApplicationList", () => { + const useGetApplicationTemplatesMock: jest.SpyInstance = jest.spyOn(useGetApplicationTemplates, "default"); + + useGetApplicationTemplatesMock.mockImplementation(() => ({ + data: applicationTemplatesListMockResponse, + error: null, + isLoading: false, + isValidating: false, + mutate: jest.fn() + })); + + const gridComponent: jest.SpyInstance = jest.spyOn(Grid, "default"); + + gridComponent.mockImplementation((props: PropsWithChildren) =>
{ props?.children }
); + + const props: ApplicationListPropsInterface = { + list: applicationListMockResponse + }; + + test("Test whether the application list correctly identifies the created app template", async () => { + render( + + + + ); + + await waitFor(() => { + expect(screen.getByText(TEMPLATE_NAMES.salesforce)).toBeInTheDocument(); + expect(screen.getByText(TEMPLATE_NAMES.spa)).toBeInTheDocument(); + }); + }); +}); diff --git a/features/admin.applications.v1/__tests__/components/application-templates/application-creation-adapter.test.tsx b/features/admin.applications.v1/__tests__/components/application-templates/application-creation-adapter.test.tsx new file mode 100644 index 00000000000..2430ae0df19 --- /dev/null +++ b/features/admin.applications.v1/__tests__/components/application-templates/application-creation-adapter.test.tsx @@ -0,0 +1,73 @@ +/** + * Copyright (c) 2024, WSO2 LLC. (https://www.wso2.com). + * + * WSO2 LLC. licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import * as getCORSOrigins from "@wso2is/admin.core.v1/api/cors-configurations"; +import React from "react"; +import { render, screen, waitFor } from "../../../../test-configs"; +import ApplicationCreationAdapter, { + ApplicationCreationAdapterPropsInterface +} from "../../../components/application-templates/application-creation-adapter"; +import * as OauthProtocolSettingsWizardForm from "../../../components/wizard/oauth-protocol-settings-wizard-form"; +import { + applicationTemplatesListMockResponse +} from "../../__mocks__/application-template"; +import "@testing-library/jest-dom"; + +describe("[Applications Management Feature] - ApplicationCreationAdapter", () => { + const OauthProtocolSettingsWizardFormMock: jest.SpyInstance = jest.spyOn( + OauthProtocolSettingsWizardForm, "OauthProtocolSettingsWizardForm"); + + OauthProtocolSettingsWizardFormMock.mockImplementation(() => jest.fn()); + + const getCORSOriginsMock: jest.SpyInstance = jest.spyOn( + getCORSOrigins, "getCORSOrigins"); + + getCORSOriginsMock.mockImplementation(() => Promise.resolve([])); + + const propsWithSPATemplate: ApplicationCreationAdapterPropsInterface = { + onClose: jest.fn(), + showWizard: true, + template: applicationTemplatesListMockResponse[0] + }; + + const propsWithSalesforceTemplate: ApplicationCreationAdapterPropsInterface = { + onClose: jest.fn(), + showWizard: true, + template: applicationTemplatesListMockResponse[3] + }; + + test("Test the existing technology/protocol based application creation wizard", async () => { + render( + + ); + + await waitFor(() => { + expect(screen.getByTestId("minimal-application-create-wizard-modal")).toBeInTheDocument(); + }); + }); + + test("Test the dynamically rendered application creation wizard", async () => { + render( + + ); + + await waitFor(() => { + expect(screen.getByTestId("application-create-wizard")).toBeInTheDocument(); + }); + }); +}); diff --git a/features/admin.applications.v1/__tests__/components/application-templates/application-template-card.test.tsx b/features/admin.applications.v1/__tests__/components/application-templates/application-template-card.test.tsx new file mode 100644 index 00000000000..234dc2db7bf --- /dev/null +++ b/features/admin.applications.v1/__tests__/components/application-templates/application-template-card.test.tsx @@ -0,0 +1,67 @@ +/** + * Copyright (c) 2024, WSO2 LLC. (https://www.wso2.com). + * + * WSO2 LLC. licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import * as Card from "@oxygen-ui/react/Card"; +import cloneDeep from "lodash-es/cloneDeep"; +import React, { PropsWithChildren } from "react"; +import { render, screen, waitFor } from "../../../../test-configs"; +import ApplicationTemplateCard, { + ApplicationTemplateCardPropsInterface +} from "../../../components/application-templates/application-template-card"; +import { + applicationTemplatesListMockResponse +} from "../../__mocks__/application-template"; +import "@testing-library/jest-dom"; + +describe("[Applications Management Feature] - ApplicationTemplateCard", () => { + const props: ApplicationTemplateCardPropsInterface = { + onClick: jest.fn(), + template: cloneDeep(applicationTemplatesListMockResponse[3]) + }; + + const cardComponent: jest.SpyInstance = jest.spyOn(Card, "default"); + + cardComponent.mockImplementation((props: PropsWithChildren) =>
{ props?.children }
); + + test("Test the rendering of the application creation template card component", async () => { + render( + + ); + + await waitFor(() => { + expect(screen.getByText(applicationTemplatesListMockResponse[3]?.name)).toBeInTheDocument(); + }); + }); + + test("Test the 'Coming Soon' label of the application template card component", async () => { + props.template.customAttributes = [ + { + key: "comingSoon", + value: "true" + } + ]; + + render( + + ); + + await waitFor(() => { + expect(screen.getByText("common:comingSoon")).toBeInTheDocument(); + }); + }); +}); diff --git a/features/admin.applications.v1/__tests__/components/application-templates/application-template-grid.test.tsx b/features/admin.applications.v1/__tests__/components/application-templates/application-template-grid.test.tsx new file mode 100644 index 00000000000..84a9924e248 --- /dev/null +++ b/features/admin.applications.v1/__tests__/components/application-templates/application-template-grid.test.tsx @@ -0,0 +1,176 @@ +/** + * Copyright (c) 2024, WSO2 LLC. (https://www.wso2.com). + * + * WSO2 LLC. licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import * as Card from "@oxygen-ui/react/Card"; +import React, { PropsWithChildren } from "react"; +import { fireEvent, render, screen, waitFor, within } from "../../../../test-configs"; +import * as useGetApplicationTemplates from "../../../api/use-get-application-templates"; +import ApplicationTemplateGrid, { + ApplicationTemplateGridPropsInterface +} from "../../../components/application-templates/application-templates-grid"; +import { CategorizedApplicationTemplatesInterface } from "../../../models/application-templates"; +import ApplicationTemplatesProvider from "../../../provider/application-templates-provider"; +import { ApplicationManagementUtils } from "../../../utils/application-management-utils"; +import { + applicationTemplatesListMockResponse, categorizedApplicationTemplatesListMockResponse +} from "../../__mocks__/application-template"; +import "@testing-library/jest-dom"; + +jest.mock("@wso2is/admin.core.v1", () => { + const adminCore: Record = jest.requireActual("@wso2is/admin.core.v1"); + + return { + ...adminCore, + getEmptyPlaceholderIllustrations: jest.fn().mockReturnValue({}) + }; +}); + +describe("[Applications Management Feature] - ApplicationTemplateGrid", () => { + const props: ApplicationTemplateGridPropsInterface = { + onTemplateSelect: jest.fn() + }; + + const useGetApplicationTemplatesMock: jest.SpyInstance = jest.spyOn(useGetApplicationTemplates, "default"); + + useGetApplicationTemplatesMock.mockImplementation(() => ({ + data: applicationTemplatesListMockResponse, + error: null, + isLoading: false, + isValidating: false, + mutate: jest.fn() + })); + + const getCustomInboundProtocolsMock: jest.SpyInstance = jest.spyOn( + ApplicationManagementUtils, "getCustomInboundProtocols"); + + getCustomInboundProtocolsMock.mockImplementation(() => []); + + const cardComponent: jest.SpyInstance = jest.spyOn(Card, "default"); + + cardComponent.mockImplementation((props: PropsWithChildren) =>
{ props?.children }
); + + /** + * Perform the assertion to ensure that the correct application template + * cards are being displayed. + * + * @param empty - Whether the expected search result is empty or not. + */ + const assert = async (empty: boolean = false): Promise => { + await waitFor(() => { + if (empty) { + expect(screen.queryByText("Salesforce")).not.toBeInTheDocument(); + } else { + expect(screen.getByText("Salesforce")).toBeInTheDocument(); + } + expect(screen.queryByText("Single-Page Application")).not.toBeInTheDocument(); + expect(screen.queryByText("Traditional Web Application")).not.toBeInTheDocument(); + expect(screen.queryByText("Standard-Based Application")).not.toBeInTheDocument(); + categorizedApplicationTemplatesListMockResponse.forEach( + (category: CategorizedApplicationTemplatesInterface) => { + expect(screen.queryByText(category.displayName)).not.toBeInTheDocument(); + expect(screen.queryByText(category?.description ?? "")).not.toBeInTheDocument(); + } + ); + }); + }; + + test("Test the rendering of the application template grid component", async () => { + render( + + + + ); + + await waitFor(() => { + categorizedApplicationTemplatesListMockResponse.forEach( + (category: CategorizedApplicationTemplatesInterface) => { + if (category?.templates?.length > 0) { + expect(screen.getByText(category.displayName)).toBeInTheDocument(); + expect(screen.getByText(category?.description ?? "")).toBeInTheDocument(); + } + } + ); + }); + }); + + test("Test the search functionality of the template grid component", async () => { + render( + + + + ); + + fireEvent.change( + within(screen.getByTestId("search-with-filters")).getByRole("textbox"), + { target: { value: "Salesforce" } } + ); + + await assert(); + + fireEvent.change( + within(screen.getByTestId("search-with-filters")).getByRole("textbox"), + { target: { value: "salesforce" } } + ); + + await assert(); + + fireEvent.change( + within(screen.getByTestId("search-with-filters")).getByRole("textbox"), + { target: { value: "sa" } } + ); + fireEvent.click( + within(screen.getByTestId("search-with-filters")).getByText("SSO") + ); + + await assert(); + + fireEvent.click( + within(screen.getByTestId("search-with-filters")).getByText("SSO") + ); + fireEvent.change( + within(screen.getByTestId("search-with-filters")).getByRole("textbox"), + { target: { value: "this is a query to get the empty search result" } } + ); + + await assert(true); + await waitFor(() => { + expect(screen.getByTestId("application-template-grid-empty-search-placeholder")).toBeInTheDocument(); + }); + }); + + test("Test the empty placeholder when the application template list is empty", async () => { + useGetApplicationTemplatesMock.mockImplementation(() => ({ + data: [], + error: null, + isLoading: false, + isValidating: false, + mutate: jest.fn() + })); + + render( + + + + ); + + await assert(true); + await waitFor(() => { + expect(screen.getByTestId("application-template-grid-empty-placeholder")).toBeInTheDocument(); + }); + }); +}); diff --git a/features/admin.applications.v1/__tests__/components/dynamic-forms/application-create-wizard.test.tsx b/features/admin.applications.v1/__tests__/components/dynamic-forms/application-create-wizard.test.tsx new file mode 100644 index 00000000000..7db7ee1c475 --- /dev/null +++ b/features/admin.applications.v1/__tests__/components/dynamic-forms/application-create-wizard.test.tsx @@ -0,0 +1,182 @@ +/** + * Copyright (c) 2024, WSO2 LLC. (https://www.wso2.com). + * + * WSO2 LLC. licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import MuiTextField, { TextFieldProps as MuiTextFieldProps } from "@mui/material/TextField"; +import * as OxygenTextField from "@oxygen-ui/react/TextField"; +import cloneDeep from "lodash-es/cloneDeep"; +import set from "lodash-es/set"; +import React from "react"; +import { fireEvent, render, screen, waitFor, within } from "../../../../test-configs"; +import * as api from "../../../api/application"; +import * as useGetApplicationTemplate from "../../../api/use-get-application-template"; +import * as useGetApplicationTemplateMetadata from "../../../api/use-get-application-template-metadata"; +import { + ApplicationCreateWizard, + ApplicationCreateWizardPropsInterface +} from "../../../components/dynamic-forms/application-create-wizard"; +import * as useApplicationSharingEligibility from "../../../hooks/use-application-sharing-eligibility"; +import { MainApplicationInterface } from "../../../models"; +import { ApplicationTemplateMetadataInterface } from "../../../models/application-templates"; +import { applicationSearchListMockResponse } from "../../__mocks__/application"; +import { + applicationTemplateMetadataMockResponse, + applicationTemplateMockResponse, + applicationTemplatesListMockResponse +} from "../../__mocks__/application-template"; +import "@testing-library/jest-dom"; + +describe("[Applications Management Feature] - ApplicationCreateWizard", () => { + const useGetApplicationTemplateMock: jest.SpyInstance = jest.spyOn(useGetApplicationTemplate, "default"); + + useGetApplicationTemplateMock.mockImplementation(() => ({ + data: applicationTemplateMockResponse, + error: null, + isLoading: false, + isValidating: false, + mutate: jest.fn() + })); + + const useGetApplicationTemplateMetadataMock: jest.SpyInstance = jest.spyOn( + useGetApplicationTemplateMetadata, "default"); + + useGetApplicationTemplateMetadataMock.mockImplementation(() => ({ + data: applicationTemplateMetadataMockResponse, + error: null, + isLoading: false, + isValidating: false, + mutate: jest.fn() + })); + + const useApplicationListMock: jest.SpyInstance = jest.spyOn(api, "useApplicationList"); + + useApplicationListMock.mockImplementation(() => ({ + data: applicationSearchListMockResponse, + error: null, + isLoading: false, + isValidating: false, + mutate: jest.fn() + })); + + const getApplicationListMock: jest.SpyInstance = jest.spyOn(api, "getApplicationList"); + + getApplicationListMock.mockImplementation(() => Promise.resolve(applicationSearchListMockResponse)); + + const createApplicationMock: jest.SpyInstance = jest.spyOn(api, "createApplication"); + + createApplicationMock.mockImplementation(() => Promise.resolve()); + + const textFieldMock: jest.SpyInstance = jest.spyOn(OxygenTextField, "default"); + + textFieldMock.mockImplementation((props: MuiTextFieldProps) => { + const { id, type, ...rest } = props; + + return ( +
+ +
+ ); + }); + + const useApplicationSharingEligibilityMock: jest.SpyInstance = jest.spyOn( + useApplicationSharingEligibility, "default"); + + useApplicationSharingEligibilityMock.mockImplementation(jest.fn().mockReturnValue(true)); + + const props: ApplicationCreateWizardPropsInterface = { + onClose: jest.fn(), + template: applicationTemplatesListMockResponse[3] + }; + + test("Test the rendering of the dynamic application create wizard based on the template", async () => { + render( + + ); + + await waitFor(() => { + expect(screen.getByTestId("application-create-wizard")).toBeInTheDocument(); + expect(screen.getByText("Salesforce")).toBeInTheDocument(); + expect(screen.getByTestId("salesforce-create-wizard-application-name")).toBeInTheDocument(); + expect(screen.getByTestId("application-create-wizard-share-field")).toBeInTheDocument(); + expect(screen.getByText("applications:wizards.minimalAppCreationWizard.help.heading")).toBeInTheDocument(); + expect(screen.getByText("https://myapp.io/login")).toBeInTheDocument(); + }); + }); + + test("Test whether the unique application name is generated", async () => { + render( + + ); + + await waitFor(() => { + expect(within(screen.getByTestId("salesforce-create-wizard-application-name")) + .getByRole("textbox")).toHaveValue("Salesforce 2"); + }); + }); + + test("Test whether the form submission is completed with only defined fields", async () => { + render( + + ); + + fireEvent.click(screen.getByTestId("application-create-wizard-create-button")); + + await waitFor(() => { + expect(createApplicationMock).toHaveBeenCalledWith({ + name: "Salesforce 2", + templateId: "salesforce" + }); + }); + }); + + test("Test whether the form submission is completed with initial values and input values", async () => { + const clonedApplicationTemplateMetadataMockResponse: ApplicationTemplateMetadataInterface = cloneDeep( + applicationTemplateMetadataMockResponse); + + set( + clonedApplicationTemplateMetadataMockResponse, + "create.form.submitDefinedFieldsOnly", + false + ); + + useGetApplicationTemplateMetadataMock.mockImplementation(() => ({ + data: clonedApplicationTemplateMetadataMockResponse, + error: null, + isLoading: false, + isValidating: false, + mutate: jest.fn() + })); + + render( + + ); + + fireEvent.change( + within(screen.getByTestId("salesforce-create-wizard-application-name")).getByRole("textbox"), + { target: { value: "New Salesforce App" } } + ); + fireEvent.click(screen.getByTestId("application-create-wizard-create-button")); + + const appPayload: MainApplicationInterface = cloneDeep(applicationTemplateMockResponse.payload); + + appPayload.name = "New Salesforce App"; + + await waitFor(() => { + expect(createApplicationMock).toHaveBeenCalledWith(appPayload); + }); + }); +}); diff --git a/features/admin.applications.v1/__tests__/components/dynamic-forms/application-edit-form.test.tsx b/features/admin.applications.v1/__tests__/components/dynamic-forms/application-edit-form.test.tsx new file mode 100644 index 00000000000..b7fbbbf2089 --- /dev/null +++ b/features/admin.applications.v1/__tests__/components/dynamic-forms/application-edit-form.test.tsx @@ -0,0 +1,128 @@ +/** + * Copyright (c) 2024, WSO2 LLC. (https://www.wso2.com). + * + * WSO2 LLC. licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import MuiTextField, { TextFieldProps as MuiTextFieldProps } from "@mui/material/TextField"; +import * as OxygenTextField from "@oxygen-ui/react/TextField"; +import cloneDeep from "lodash-es/cloneDeep"; +import set from "lodash-es/set"; +import React from "react"; +import { fireEvent, render, screen, waitFor, within } from "../../../../test-configs"; +import * as api from "../../../api/application"; +import { + ApplicationEditForm, + ApplicationEditFormPropsInterface +} from "../../../components/dynamic-forms/application-edit-form"; +import { MainApplicationInterface } from "../../../models"; +import { ApplicationEditTabMetadataInterface } from "../../../models/application-templates"; +import { applicationMockResponse, applicationSearchListMockResponse } from "../../__mocks__/application"; +import { dynamicApplicationEditTabMetadataMockObject } from "../../__mocks__/application-template"; +import "@testing-library/jest-dom"; + +describe("[Applications Management Feature] - ApplicationEditForm", () => { + const getApplicationListMock: jest.SpyInstance = jest.spyOn(api, "getApplicationList"); + + getApplicationListMock.mockImplementation(() => Promise.resolve(applicationSearchListMockResponse)); + + const updateApplicationMock: jest.SpyInstance = jest.spyOn(api, "updateApplicationDetails"); + + updateApplicationMock.mockImplementation(() => Promise.resolve()); + + const textFieldMock: jest.SpyInstance = jest.spyOn(OxygenTextField, "default"); + + textFieldMock.mockImplementation((props: MuiTextFieldProps) => { + const { id, type, ...rest } = props; + + return ( +
+ +
+ ); + }); + + const props: ApplicationEditFormPropsInterface = { + application: applicationMockResponse, + isLoading: false, + onUpdate: jest.fn(), + readOnly: false, + tab: dynamicApplicationEditTabMetadataMockObject + }; + + test("Test the rendering of the dynamic application edit tab based on the metadata", async () => { + render( + + ); + + await waitFor(() => { + expect(screen.getByTestId("application-edit-form-tab-salesforce-settings")).toBeInTheDocument(); + expect(screen.getByTestId("dynamic-application-edit-form-application-name")).toBeInTheDocument(); + expect(screen.getByTestId("dynamic-application-edit-form-application-description")).toBeInTheDocument(); + }); + }); + + test("Test whether the form submission is completed with only defined fields", async () => { + render( + + ); + + fireEvent.click(screen.getByTestId("application-edit-form-update-button")); + + await waitFor(() => { + expect(updateApplicationMock).toHaveBeenCalledWith({ + description: applicationMockResponse?.description, + id: applicationMockResponse?.id, + name: applicationMockResponse?.name + }); + }); + }); + + test("Test whether the form submission is completed with initial values and input values", async () => { + const clonedApplicationEditTabMetadataMockResponse: ApplicationEditTabMetadataInterface = cloneDeep( + dynamicApplicationEditTabMetadataMockObject); + + set( + clonedApplicationEditTabMetadataMockResponse, + "form.submitDefinedFieldsOnly", + false + ); + + props.tab = clonedApplicationEditTabMetadataMockResponse; + + render( + + ); + + fireEvent.change( + within(screen.getByTestId("dynamic-application-edit-form-application-name")).getByRole("textbox"), + { target: { value: "Test App" } } + ); + fireEvent.change( + within(screen.getByTestId("dynamic-application-edit-form-application-description")).getByRole("textbox"), + { target: { value: "Test Description" } } + ); + fireEvent.click(screen.getByTestId("application-edit-form-update-button")); + + const appPayload: MainApplicationInterface = cloneDeep(applicationMockResponse); + + appPayload.name = "Test App"; + appPayload.description = "Test Description"; + + await waitFor(() => { + expect(updateApplicationMock).toHaveBeenCalledWith(appPayload); + }); + }); +}); diff --git a/features/admin.applications.v1/__tests__/components/help-panel/markdown-guide.test.tsx b/features/admin.applications.v1/__tests__/components/help-panel/markdown-guide.test.tsx new file mode 100644 index 00000000000..2725f585e53 --- /dev/null +++ b/features/admin.applications.v1/__tests__/components/help-panel/markdown-guide.test.tsx @@ -0,0 +1,43 @@ +/** + * Copyright (c) 2024, WSO2 LLC. (https://www.wso2.com). + * + * WSO2 LLC. licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import React from "react"; +import { render, screen, waitFor } from "../../../../test-configs"; +import { MarkdownGuide, MarkdownGuidePropsInterface } from "../../../components/help-panel/markdown-guide"; +import { ApplicationEditTabMetadataInterface } from "../../../models/application-templates"; +import { applicationTemplateMetadataMockResponse } from "../../__mocks__/application-template"; +import "@testing-library/jest-dom"; + +describe("[Applications Management Feature] - MarkdownGuide", () => { + const props: MarkdownGuidePropsInterface = { + content: applicationTemplateMetadataMockResponse?.edit?.tabs.find( + (tab: ApplicationEditTabMetadataInterface) => tab?.id === "quick-start")?.guide ?? "", + isLoading: false + }; + + test("Test the rendering of the markdown guide component", async () => { + render( + + ); + + await waitFor(() => { + expect(screen.getByText("Dillinger")).toBeInTheDocument(); + expect(screen.getByText("_The Last Markdown Editor")).toBeInTheDocument(); + }); + }); +}); diff --git a/features/admin.applications.v1/__tests__/hooks/use-dynamic-field-validation.test.tsx b/features/admin.applications.v1/__tests__/hooks/use-dynamic-field-validation.test.tsx new file mode 100644 index 00000000000..0d86f3cbfb8 --- /dev/null +++ b/features/admin.applications.v1/__tests__/hooks/use-dynamic-field-validation.test.tsx @@ -0,0 +1,125 @@ +/** + * Copyright (c) 2024, WSO2 LLC. (https://www.wso2.com). + * + * WSO2 LLC. licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import cloneDeep from "lodash-es/cloneDeep"; +import React from "react"; +import { render, screen, waitFor } from "../../../test-configs"; +import * as api from "../../api/application"; +import { ApplicationManagementConstants } from "../../constants"; +import { applicationSearchListMockResponse } from "../__mocks__/application"; +import { + applicationNameDynamicFormFieldMock, + domainNameDynamicFormFieldMock +} from "../__mocks__/application-template"; +import "@testing-library/jest-dom"; +import MockDynamicFieldValidationComponent, { + MockDynamicFieldValidationComponentProps +} from "../__mocks__/mock-dynamic-field-validation-component"; + +describe("[Applications Management Feature] - useDynamicFieldValidations", () => { + const getApplicationListMock: jest.SpyInstance = jest.spyOn(api, "getApplicationList"); + + getApplicationListMock.mockImplementation(() => Promise.resolve(applicationSearchListMockResponse)); + + const props: MockDynamicFieldValidationComponentProps = { + field: cloneDeep(applicationNameDynamicFormFieldMock), + formValues: { name: "" } + }; + + test("Test the validation for the required field", async () => { + render( + + ); + + await waitFor(() => { + expect(screen.getByText("applications:forms.dynamicApplicationCreateWizard.common.validations.required")) + .toBeInTheDocument(); + }); + }); + + test("Test the validation for the not required field", async () => { + props.field.required = false; + + render( + + ); + + await waitFor(() => { + expect(screen.queryByText("applications:forms.dynamicApplicationCreateWizard.common.validations.required")) + .not.toBeInTheDocument(); + expect(screen.getByText(`applications:forms.spaProtocolSettingsWizard.fields.name.validations.invalid.${ + ApplicationManagementConstants.FORM_FIELD_CONSTRAINTS.APP_NAME_MAX_LENGTH + }`)).toBeInTheDocument(); + }); + }); + + test("Test the validation for the invalid application name", async () => { + props.field.required = true; + props.formValues.name = "inv@lid n@me!"; + + render( + + ); + + await waitFor(() => { + expect(screen.getByText(`applications:forms.spaProtocolSettingsWizard.fields.name.validations.invalid${ + props.formValues.name + }.${ + ApplicationManagementConstants.FORM_FIELD_CONSTRAINTS.APP_NAME_MAX_LENGTH + }`)).toBeInTheDocument(); + }); + }); + + test("Test the validation for the duplicate application name", async () => { + props.formValues.name = "Salesforce"; + + render( + + ); + + await waitFor(() => { + expect(screen.getByText("applications:forms.generalDetails.fields.name.validations.duplicate")) + .toBeInTheDocument(); + }); + }); + + test("Test the validation for the invalid domain name", async () => { + props.field = domainNameDynamicFormFieldMock; + props.formValues = { + advancedConfigurations: { + additionalSpProperties: [ + { + displayName: "Domain Name", + name: "domainName", + value: ".ex@mple.cominavlid" + } + ] + }, + name: "Salesforce" + }; + + render( + + ); + + await waitFor(() => { + expect(screen.getByText("applications:forms.dynamicApplicationCreateWizard.domainName.validations.invalid")) + .toBeInTheDocument(); + }); + }); +}); diff --git a/features/admin.applications.v1/__tests__/pages/application-edit.test.tsx b/features/admin.applications.v1/__tests__/pages/application-edit.test.tsx new file mode 100644 index 00000000000..f1647f41e36 --- /dev/null +++ b/features/admin.applications.v1/__tests__/pages/application-edit.test.tsx @@ -0,0 +1,193 @@ +/** + * Copyright (c) 2024, WSO2 LLC. (https://www.wso2.com). + * + * WSO2 LLC. licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import * as getCORSOrigins from "@wso2is/admin.core.v1/api/cors-configurations"; +import * as useUIConfig from "@wso2is/admin.core.v1/hooks/use-ui-configs"; +import { I18n } from "@wso2is/i18n"; +import { createMemoryHistory } from "history"; +import React from "react"; +import { render, screen, waitFor, within } from "../../../test-configs"; +import * as api from "../../api/application"; +import * as useGetApplication from "../../api/use-get-application"; +import * as useGetApplicationTemplateMetadata from "../../api/use-get-application-template-metadata"; +import * as useGetApplicationTemplates from "../../api/use-get-application-templates"; +import ApplicationEditPage, { ApplicationEditPageInterface } from "../../pages/application-edit"; +import ApplicationTemplatesProvider from "../../provider/application-templates-provider"; +import { ApplicationManagementUtils } from "../../utils/application-management-utils"; +import { + applicationMockResponse, + applicationSearchListMockResponse, + spaApplicationMockResponse +} from "../__mocks__/application"; +import { + applicationTemplateMetadataMockResponse, + applicationTemplatesListMockResponse +} from "../__mocks__/application-template"; +import "@testing-library/jest-dom"; + +describe("[Applications Management Feature] - ApplicationEditPage", () => { + const useGetApplicationTemplatesMock: jest.SpyInstance = jest.spyOn(useGetApplicationTemplates, "default"); + + useGetApplicationTemplatesMock.mockImplementation(() => ({ + data: applicationTemplatesListMockResponse, + error: null, + isLoading: false, + isValidating: false, + mutate: jest.fn() + })); + + const useGetApplicationMock: jest.SpyInstance = jest.spyOn(useGetApplication, "useGetApplication"); + + useGetApplicationMock.mockImplementation(() => ({ + data: spaApplicationMockResponse, + error: null, + isLoading: false, + isValidating: false, + mutate: jest.fn() + })); + + const getApplicationListMock: jest.SpyInstance = jest.spyOn(api, "getApplicationList"); + + getApplicationListMock.mockImplementation(() => Promise.resolve(applicationSearchListMockResponse)); + + const useUIConfigMock: jest.SpyInstance = jest.spyOn(useUIConfig, "default"); + + useUIConfigMock.mockImplementation(() => ({ + UIConfig: { + legacyMode: { + organizations: true + } + } + })); + + const getInboundProtocolsMock: jest.SpyInstance = jest.spyOn( + ApplicationManagementUtils, "getInboundProtocols"); + + getInboundProtocolsMock.mockImplementation(() => Promise.resolve()); + + const getInboundProtocolConfigMock: jest.SpyInstance = jest.spyOn(api, "getInboundProtocolConfig"); + + getInboundProtocolConfigMock.mockImplementation(() => Promise.resolve({})); + + const getSAMLApplicationMetaMock: jest.SpyInstance = jest.spyOn( + ApplicationManagementUtils, "getSAMLApplicationMeta"); + + getSAMLApplicationMetaMock.mockImplementation(() => Promise.resolve()); + + const getOIDCApplicationMetaMock: jest.SpyInstance = jest.spyOn( + ApplicationManagementUtils, "getOIDCApplicationMeta"); + + getOIDCApplicationMetaMock.mockImplementation(() => Promise.resolve()); + + const getCORSOriginsMock: jest.SpyInstance = jest.spyOn( + getCORSOrigins, "getCORSOrigins"); + + getCORSOriginsMock.mockImplementation(() => Promise.resolve([])); + + const translateMock: jest.SpyInstance = jest.spyOn(I18n.instance, "t"); + + translateMock.mockImplementation((key: string) => key); + + const props: ApplicationEditPageInterface = { + history: createMemoryHistory(), + location: { + hash: "", + pathname: "/t/carbon.super/console/applications/c3a3d3ce-4166-4e6a-963a-94e1c1c8de5f", + search: "", + state: null + }, + match: { + isExact: true, + params: { + id: "c3a3d3ce-4166-4e6a-963a-94e1c1c8de5f" + }, + path: "/t/carbon.super/console/applications/c3a3d3ce-4166-4e6a-963a-94e1c1c8de5f", + url: "https://localhost:9001/t/carbon.super/console/applications/c3a3d3ce-4166-4e6a-963a-94e1c1c8de5f" + } + }; + + test("Test the rendering of the application edit page component for a SPA app", async () => { + render( + + + + ); + + await waitFor(() => { + expect(screen.getByTestId("application-edit-page-layout")).toBeInTheDocument(); + + const tabs: string[] = [ + "applications:edit.sections.general.tabName", + "applications:edit.sections.access.tabName", + "applications:edit.sections.attributes.tabName", + "applications:edit.sections.signOnMethod.tabName", + "extensions:develop.applications.edit.sections.apiAuthorization.title", + "extensions:develop.applications.edit.sections.roles.heading", + "applications:edit.sections.provisioning.tabName", + "applications:edit.sections.advanced.tabName", + "applications:edit.sections.sharedAccess.tabName", + "applications:edit.sections.info.tabName" + ]; + + tabs.forEach((tabName: string) => expect( + within(screen.getByTestId("application-edit-resource-tabs")).getByText(tabName)).toBeInTheDocument()); + }); + }); + + test("Test the rendering of the application edit page component for a SSO app", async () => { + const useGetApplicationTemplateMetadataMock: jest.SpyInstance = jest.spyOn( + useGetApplicationTemplateMetadata, "default"); + + useGetApplicationTemplateMetadataMock.mockImplementation(() => ({ + data: applicationTemplateMetadataMockResponse, + error: null, + isLoading: false, + isValidating: false, + mutate: jest.fn() + })); + + useGetApplicationMock.mockImplementation(() => ({ + data: applicationMockResponse, + error: null, + isLoading: false, + isValidating: false, + mutate: jest.fn() + })); + + render( + + + + ); + + await waitFor(() => { + expect(screen.getByTestId("application-edit-page-layout")).toBeInTheDocument(); + + const tabs: string[] = [ + "Quick Start", + "applications:edit.sections.general.tabName", + "Application Roles", + "Attributes", + "Salesforce Settings" + ]; + + tabs.forEach((tabName: string) => expect( + within(screen.getByTestId("application-edit-resource-tabs")).getByText(tabName)).toBeInTheDocument()); + }); + }); +}); diff --git a/features/admin.applications.v1/__tests__/pages/application-template.test.tsx b/features/admin.applications.v1/__tests__/pages/application-template.test.tsx new file mode 100644 index 00000000000..4006d8abf5e --- /dev/null +++ b/features/admin.applications.v1/__tests__/pages/application-template.test.tsx @@ -0,0 +1,139 @@ +/** + * Copyright (c) 2024, WSO2 LLC. (https://www.wso2.com). + * + * WSO2 LLC. licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import * as Card from "@oxygen-ui/react/Card"; +import * as getCORSOrigins from "@wso2is/admin.core.v1/api/cors-configurations"; +import React, { PropsWithChildren } from "react"; +import { fireEvent, render, screen, waitFor, within } from "../../../test-configs"; +import * as useGetApplicationTemplates from "../../api/use-get-application-templates"; +import { + ApplicationTemplateCardPropsInterface +} from "../../components/application-templates/application-template-card"; +import * as OauthProtocolSettingsWizardForm from "../../components/wizard/oauth-protocol-settings-wizard-form"; +import { CategorizedApplicationTemplatesInterface } from "../../models/application-templates"; +import ApplicationTemplateSelectPage from "../../pages/application-template"; +import ApplicationTemplatesProvider from "../../provider/application-templates-provider"; +import { ApplicationManagementUtils } from "../../utils/application-management-utils"; +import { + applicationTemplatesListMockResponse, + categorizedApplicationTemplatesListMockResponse +} from "../__mocks__/application-template"; +import "@testing-library/jest-dom"; + +describe("[Applications Management Feature] - ApplicationTemplateSelectPage", () => { + const useGetApplicationTemplatesMock: jest.SpyInstance = jest.spyOn(useGetApplicationTemplates, "default"); + + useGetApplicationTemplatesMock.mockImplementation(() => ({ + data: applicationTemplatesListMockResponse, + error: null, + isLoading: false, + isValidating: false, + mutate: jest.fn() + })); + + const OauthProtocolSettingsWizardFormMock: jest.SpyInstance = jest.spyOn( + OauthProtocolSettingsWizardForm, "OauthProtocolSettingsWizardForm"); + + OauthProtocolSettingsWizardFormMock.mockImplementation(() => jest.fn()); + + const getCustomInboundProtocolsMock: jest.SpyInstance = jest.spyOn( + ApplicationManagementUtils, "getCustomInboundProtocols"); + + getCustomInboundProtocolsMock.mockImplementation(() => []); + + const getCORSOriginsMock: jest.SpyInstance = jest.spyOn( + getCORSOrigins, "getCORSOrigins"); + + getCORSOriginsMock.mockImplementation(() => Promise.resolve([])); + + const cardComponent: jest.SpyInstance = jest.spyOn(Card, "default"); + + cardComponent.mockImplementation((props: PropsWithChildren) => { + return ( +
+ { props?.children } +
+ ); + }); + + test("Test the rendering of the application template selection page", async () => { + render( + + + + ); + + await waitFor(() => { + expect(screen.getByText("console:develop.pages.applicationTemplate.title")).toBeInTheDocument(); + expect(screen.getByText("console:develop.pages.applicationTemplate.subTitle")).toBeInTheDocument(); + expect(screen.getByTestId("application-template-card-single-page-application")).toBeInTheDocument(); + expect(screen.getByTestId("application-template-card-traditional-web-application")).toBeInTheDocument(); + expect(screen.getByTestId("application-template-card-custom-application")).toBeInTheDocument(); + expect(screen.getByTestId("application-template-card-salesforce")).toBeInTheDocument(); + categorizedApplicationTemplatesListMockResponse.forEach( + (category: CategorizedApplicationTemplatesInterface) => { + if (category?.templates?.length > 0) { + expect(screen.getByText(category.displayName)).toBeInTheDocument(); + expect(screen.getByText(category?.description ?? "")).toBeInTheDocument(); + } + } + ); + }); + }); + + test("Test the single page application create wizard", async () => { + render( + + + + ); + + fireEvent.click(screen.getByTestId("application-template-card-single-page-application")); + + await waitFor(() => { + expect(screen.getByTestId("minimal-application-create-wizard-modal")).toBeInTheDocument(); + expect( + within(screen.getByTestId("minimal-application-create-wizard-modal")) + .getByText("Single-Page Application") + ).toBeInTheDocument(); + fireEvent.click( + within(screen.getByTestId("minimal-application-create-wizard-modal")).getByTestId("link-button")); + }); + }); + + test("Test the salesforce application create wizard", async () => { + render( + + + + ); + + fireEvent.click(screen.getByTestId("application-template-card-salesforce")); + + await waitFor(() => { + expect(screen.getByTestId("application-create-wizard")).toBeInTheDocument(); + expect( + within(screen.getByTestId("application-create-wizard")) + .getByText("Salesforce") + ).toBeInTheDocument(); + }); + }); +}); diff --git a/features/admin.applications.v1/__tests__/provider/application-templates-provider.test.tsx b/features/admin.applications.v1/__tests__/provider/application-templates-provider.test.tsx new file mode 100644 index 00000000000..d295fb721ab --- /dev/null +++ b/features/admin.applications.v1/__tests__/provider/application-templates-provider.test.tsx @@ -0,0 +1,56 @@ +/** + * Copyright (c) 2024, WSO2 LLC. (https://www.wso2.com). + * + * WSO2 LLC. licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import React from "react"; +import { render, screen } from "../../../test-configs"; +import * as useGetApplicationTemplates from "../../api/use-get-application-templates"; +import { CategorizedApplicationTemplatesInterface } from "../../models/application-templates"; +import ApplicationTemplatesProvider from "../../provider/application-templates-provider"; +import { + applicationTemplatesListMockResponse, categorizedApplicationTemplatesListMockResponse +} from "../__mocks__/application-template"; +import "@testing-library/jest-dom"; +import MockApplicationTemplatesComponent from "../__mocks__/mock-application-templates-component"; + +describe("[Applications Management Feature] - ApplicationTemplatesProvider", () => { + const useGetApplicationTemplatesMock: jest.SpyInstance = jest.spyOn(useGetApplicationTemplates, "default"); + + useGetApplicationTemplatesMock.mockImplementation(() => ({ + data: applicationTemplatesListMockResponse, + error: null, + isLoading: false, + isValidating: false, + mutate: jest.fn() + })); + + test("Test whether the application templates provider provides the application templates list correctly", () => { + render( + + + + ); + + expect(screen.getByTestId("mock-application-templates-component-loading-status")).toBeInTheDocument(); + expect(screen.getByTestId("mock-application-templates-component-templates").innerHTML) + .toContain(`Number of templates-${applicationTemplatesListMockResponse.length}`); + categorizedApplicationTemplatesListMockResponse.forEach((item: CategorizedApplicationTemplatesInterface) => { + expect(screen.getByTestId(`mock-application-templates-component-${item.id}`).innerHTML) + .toContain(`Number of templates-${item.templates.length}`); + }); + }); +}); diff --git a/features/admin.applications.v1/__tests__/utils/application-template-management-utils.test.ts b/features/admin.applications.v1/__tests__/utils/application-template-management-utils.test.ts new file mode 100644 index 00000000000..b557e005a8e --- /dev/null +++ b/features/admin.applications.v1/__tests__/utils/application-template-management-utils.test.ts @@ -0,0 +1,71 @@ +/** + * Copyright (c) 2024, WSO2 LLC. (https://www.wso2.com). + * + * WSO2 LLC. licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import "@testing-library/jest-dom"; +import { ApplicationTemplateConstants } from "../../constants/application-templates"; +import { ApplicationTemplateManagementUtils } from "../../utils/application-template-management-utils"; + +describe("[Applications Management Feature] - ApplicationTemplateManagementUtils", () => { + + describe("'resolveApplicationResourcePath' function", () => { + + test("Test the function with the console base url placeholder", async () => { + const result: string = ApplicationTemplateManagementUtils.resolveApplicationResourcePath( + `${ApplicationTemplateConstants.CONSOLE_BASE_URL_PLACEHOLDER}/test/app/template/logo.png` + ); + + expect(result).toBe("https://localhost:9001/console/test/app/template/logo.png"); + }); + + test("Test the function with a http url", async () => { + const result: string = ApplicationTemplateManagementUtils.resolveApplicationResourcePath( + "http://example.com/test/app/template/logo.svg" + ); + + expect(result).toBe("http://example.com/test/app/template/logo.svg"); + }); + + test("Test the function with a https url", async () => { + const result: string = ApplicationTemplateManagementUtils.resolveApplicationResourcePath( + "https://example.com/test/app/template/logo.jpeg" + ); + + expect(result).toBe("https://example.com/test/app/template/logo.jpeg"); + }); + + test("Test the function with a data url", async () => { + const result: string = ApplicationTemplateManagementUtils.resolveApplicationResourcePath( + "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAATTTAAAC1HAwCA" + + "AAAC0lEQVR42mP8/wcAAgAB/FRpWZkYYYASUVORK5CYII=" + ); + + expect(result).toBe( + "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAATTTAAAC1HAwCA" + + "AAAC0lEQVR42mP8/wcAAgAB/FRpWZkYYYASUVORK5CYII=" + ); + }); + + test("Test the function with a relative path", async () => { + const result: string = ApplicationTemplateManagementUtils.resolveApplicationResourcePath( + "test/app/template/logo.jpeg" + ); + + expect(result).toBe("https://localhost:9001/console/resources/applications/test/app/template/logo.jpeg"); + }); + }); +}); diff --git a/features/admin.applications.v1/__tests__/utils/build-callback-urls-with-regexp.test.ts b/features/admin.applications.v1/__tests__/utils/build-callback-urls-with-regexp.test.ts new file mode 100644 index 00000000000..ea186b75eb1 --- /dev/null +++ b/features/admin.applications.v1/__tests__/utils/build-callback-urls-with-regexp.test.ts @@ -0,0 +1,43 @@ +/** + * Copyright (c) 2024, WSO2 LLC. (https://www.wso2.com). + * + * WSO2 LLC. licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import "@testing-library/jest-dom"; +import buildCallBackUrlsWithRegExp from "../../utils/build-callback-urls-with-regexp"; + +describe("[Applications Management Feature] - buildCallBackUrlsWithRegExp", () => { + + test("Test the function with the single callback url", async () => { + const urls: string[] = [ "https://example.com/login" ]; + + const results: string[] = buildCallBackUrlsWithRegExp(urls); + + expect(results).toEqual(urls); + }); + + test("Test the function with the multiple callback url", async () => { + const results: string[] = buildCallBackUrlsWithRegExp([ + "https://example.com/login", + "https://app.example.com/login", + "https://localhost:3000/sample/oidc/login" + ]); + + expect(results).toEqual([ + "regexp=(https://example.com/login|https://app.example.com/login|https://localhost:3000/sample/oidc/login)" + ]); + }); +}); diff --git a/features/admin.extensions.v1/configs/common.tsx b/features/admin.extensions.v1/configs/common.tsx index c2a1532ea82..34fcca2c13d 100644 --- a/features/admin.extensions.v1/configs/common.tsx +++ b/features/admin.extensions.v1/configs/common.tsx @@ -151,6 +151,7 @@ export const commonConfig: CommonConfig = { } }, primaryUserstoreOnly: true, + useExtensionTestConfig: false, userEditSection: { isGuestUser: true, showEmail: true diff --git a/features/admin.extensions.v1/configs/models/common.ts b/features/admin.extensions.v1/configs/models/common.ts index f183ebbbcbf..9f3368eab47 100644 --- a/features/admin.extensions.v1/configs/models/common.ts +++ b/features/admin.extensions.v1/configs/models/common.ts @@ -1,5 +1,5 @@ /** - * Copyright (c) 2021-2023, WSO2 LLC. (https://www.wso2.com). + * Copyright (c) 2021-2024, WSO2 LLC. (https://www.wso2.com). * * WSO2 LLC. licenses this file to you under the Apache License, * Version 2.0 (the "License"); you may not use this file except @@ -51,6 +51,7 @@ export interface CommonConfig { }; }; primaryUserstoreOnly: boolean; + useExtensionTestConfig: boolean; userEditSection: { isGuestUser: boolean; showEmail: boolean; diff --git a/features/admin.extensions.v1/test-configs/__mocks__/window.ts b/features/admin.extensions.v1/test-configs/__mocks__/window.ts index 86dd914dfc7..c5ca8dee84f 100644 --- a/features/admin.extensions.v1/test-configs/__mocks__/window.ts +++ b/features/admin.extensions.v1/test-configs/__mocks__/window.ts @@ -16,559 +16,563 @@ * under the License. */ +import { commonConfig } from "../../configs"; + /** * @remarks Window Mocks. * * @remarks Document and place all the extended Window mocks in this file. */ -/** - * Custom window interface. - */ -interface CustomExtendedWindow extends Window { - AppUtils: { - getConfig: () => any; - }; -} +if (commonConfig?.useExtensionTestConfig) { + /** + * Custom window interface. + */ + interface CustomExtendedWindow extends Window { + AppUtils: { + getConfig: () => any; + }; + } -/** - * `AppUtils` Mock. - * @remarks Overrides the default `window.AppUtils.getConfig()` result. - * IMPORTANT: Constantly keep this updated by executing `window.AppUtils.getConfig()` on the browser. - */ -(window as CustomExtendedWindow & typeof globalThis).AppUtils = { - /* eslint-disable sort-keys, max-len */ - getConfig: () => { - return { - accountApp: { - commonPostLogoutUrl: true, - path: "https://localhost:9000/myaccount/overview", - tenantQualifiedPath: "https://localhost:9000/t/testorg" - }, - adminApp: { - basePath: "/t/testorg/manage", - displayName: "Manage", - path: "/t/testorg/manage/users" - }, - allowMultipleAppProtocols: false, - appBase: "", - appBaseNameForHistoryAPI: "/", - appBaseWithTenant: "/t/testorg", - clientID: "CONSOLE", - clientOrigin: "https://console.wso2iam.io", - clientOriginWithTenant: "https://console.wso2iam.io/t/testorg", - debug: false, - developerApp: { - basePath: "/t/testorg/app", - displayName: "app", - path: "/t/testorg/develop/applications" - }, - docSiteUrl: "https://is.docs.wso2.com/", - extensions: { - collaboratorUsernameRegex: "^[\\u00C0-\\u00FFa-zA-Z0-9.+\\-_]+@[a-zA-Z0-9.\\-]+\\.[a-zA-Z]{2,10}$", - community: "https://wso2iem-en-community.insided.com/ssoproxy/login?ssoType=openidconnect&returnUrl=https://wso2iem-en-community.insided.com/asgardeo-1", - feedbackEndPoint: "https://dev.portal.asgardeo.io/api/cloud/v1/feedback/", - helpCenterUrl: "https://dev.support.asgardeo.io/asp?id=asgardeo_support_login", - defaultBrandedLoginScreenCopyright: "${copyright} ${year} WSO2, Inc.", - supportEmail: "asgardeo-help@wso2.com" - }, - idpConfigs: { + /** + * `AppUtils` Mock. + * @remarks Overrides the default `window.AppUtils.getConfig()` result. + * IMPORTANT: Constantly keep this updated by executing `window.AppUtils.getConfig()` on the browser. + */ + (window as CustomExtendedWindow & typeof globalThis).AppUtils = { + /* eslint-disable sort-keys, max-len */ + getConfig: () => { + return { + accountApp: { + commonPostLogoutUrl: true, + path: "https://localhost:9000/myaccount/overview", + tenantQualifiedPath: "https://localhost:9000/t/testorg" + }, + adminApp: { + basePath: "/t/testorg/manage", + displayName: "Manage", + path: "/t/testorg/manage/users" + }, + allowMultipleAppProtocols: false, + appBase: "", + appBaseNameForHistoryAPI: "/", + appBaseWithTenant: "/t/testorg", + clientID: "CONSOLE", + clientOrigin: "https://console.wso2iam.io", + clientOriginWithTenant: "https://console.wso2iam.io/t/testorg", + debug: false, + developerApp: { + basePath: "/t/testorg/app", + displayName: "app", + path: "/t/testorg/develop/applications" + }, + docSiteUrl: "https://is.docs.wso2.com/", + extensions: { + collaboratorUsernameRegex: "^[\\u00C0-\\u00FFa-zA-Z0-9.+\\-_]+@[a-zA-Z0-9.\\-]+\\.[a-zA-Z]{2,10}$", + community: "https://wso2iem-en-community.insided.com/ssoproxy/login?ssoType=openidconnect&returnUrl=https://wso2iem-en-community.insided.com/asgardeo-1", + feedbackEndPoint: "https://dev.portal.asgardeo.io/api/cloud/v1/feedback/", + helpCenterUrl: "https://dev.support.asgardeo.io/asp?id=asgardeo_support_login", + defaultBrandedLoginScreenCopyright: "${copyright} ${year} WSO2, Inc.", + supportEmail: "asgardeo-help@wso2.com" + }, + idpConfigs: { + serverOrigin: "https://dev.api.asgardeo.io", + enablePKCE: true, + clockTolerance: 300, + responseMode: "query", + scope: [ + "SYSTEM" + ], + storage: "webWorker", + authorizeEndpointURL: "https://api.wso2iam.io/t/a/oauth2/authorize?ut=testorg", + logoutEndpointURL: "https://api.wso2iam.io/t/a/oidc/logout", + oidcSessionIFrameEndpointURL: "https://api.wso2iam.io/t/a/oidc/checksession" + }, + isSaas: true, + loginCallbackURL: "https://dev.console.asgardeo.io/login", + logoutCallbackURL: "https://dev.console.asgardeo.io", + productVersionConfig: { + productVersion: "BETA" + }, + routes: { + home: "/t/testorg/getting-started", + login: "/t/testorg/login", + logout: "/t/testorg/logout" + }, serverOrigin: "https://dev.api.asgardeo.io", - enablePKCE: true, - clockTolerance: 300, - responseMode: "query", - scope: [ - "SYSTEM" - ], - storage: "webWorker", - authorizeEndpointURL: "https://api.wso2iam.io/t/a/oauth2/authorize?ut=testorg", - logoutEndpointURL: "https://api.wso2iam.io/t/a/oidc/logout", - oidcSessionIFrameEndpointURL: "https://api.wso2iam.io/t/a/oidc/checksession" - }, - isSaas: true, - loginCallbackURL: "https://dev.console.asgardeo.io/login", - logoutCallbackURL: "https://dev.console.asgardeo.io", - productVersionConfig: { - productVersion: "BETA" - }, - routes: { - home: "/t/testorg/getting-started", - login: "/t/testorg/login", - logout: "/t/testorg/logout" - }, - serverOrigin: "https://dev.api.asgardeo.io", - serverOriginWithTenant: "https://api.wso2iam.io/t/testorg", - session: { - sessionRefreshTimeOut: 300, - userIdleWarningTimeOut: 1740, - checkSessionInterval: 5, - userIdleTimeOut: 1800 - }, - superTenant: "carbon.super", - tenant: "testorg", - tenantPath: "/t/testorg", - tenantPrefix: "t", - ui: { - appCopyright: "${copyright} ${year} WSO2, Inc.", - appTitle: "Asgardeo Console", - appName: "Console", - applicationTemplateLoadingStrategy: "LOCAL", - identityProviderTemplateLoadingStrategy: "LOCAL", - appLogoPath: "/assets/images/branding/logo.svg", - showAppSwitchButton: true, - features: { - accountSecurity: { - enabled: true, - disabledFeatures: [], - scopes: { - create: [], - read: [ - "internal_governance_view" - ], - update: [ - "internal_governance_update" - ], - delete: [] - } - }, - accountRecovery: { - enabled: true, - disabledFeatures: [], - scopes: { - create: [], - read: [ - "internal_governance_view" - ], - update: [ - "internal_governance_update" - ], - delete: [] - } - }, - applications: { - disabledFeatures: [], - enabled: true, - scopes: { - feature: [ - "console:applications" - ], - create: [ - "internal_application_mgt_create" - ], - read: [ - "internal_cors_origins_view", - "internal_application_mgt_view", - "internal_claim_meta_view", - "internal_idp_view" - ], - update: [ - "internal_application_mgt_update" - ], - delete: [ - "internal_application_mgt_delete" - ] - } - }, - approvals: { - disabledFeatures: [], - enabled: false, - scopes: { - create: [ - "internal_humantask_view" - ], - read: [ - "internal_humantask_view" - ], - update: [ - "internal_humantask_view" - ], - delete: [ - "internal_humantask_view" - ] - } - }, - attributeDialects: { - enabled: true, - disabledFeatures: [], - scopes: { - feature: [ - "console:attributes" - ], - create: [ - "internal_claim_meta_create" - ], - read: [ - "internal_userstore_view", - "internal_claim_meta_view" - ], - update: [ - "internal_claim_meta_update" - ], - delete: [ - "internal_claim_meta_delete" - ] - } - }, - branding: { - enabled: true, - disabledFeatures: [], - scopes: { - create: [ - "internal_application_mgt_update" - ], - delete: [ - "internal_application_mgt_update" - ], - read: [ - "internal_application_mgt_view" - ], - update: [ - "internal_application_mgt_update" - ] - } - }, - certificates: { - enabled: true, - disabledFeatures: [], - scopes: { - create: [ - "internal_keystore_update" - ], - read: [ - "internal_keystore_view" - ], - update: [ - "internal_keystore_update" - ], - delete: [ - "internal_keystore_update" - ] - } - }, - developerGettingStarted: { - enabled: true, - disabledFeatures: [], - scopes: { - create: [], - read: [ - "internal_application_mgt_create", - "internal_idp_create", - "internal_application_mgt_update" - ], - update: [], - delete: [] - } - }, - emailTemplates: { - enabled: true, - disabledFeatures: [], - scopes: { - create: [ - "internal_email_mgt_create" - ], - read: [ - "internal_email_mgt_view" - ], - update: [ - "internal_email_mgt_update" - ], - delete: [ - "internal_email_mgt_delete" - ] - } - }, - governanceConnectors: { - enabled: true, - disabledFeatures: [], - scopes: { - create: [], - read: [ - "internal_governance_view" - ], - update: [ - "internal_governance_update" - ], - delete: [] - } - }, - groups: { - enabled: true, - disabledFeatures: [], - scopes: { - feature: [ - "console:groups" - ], - create: [ - "internal_group_mgt_create" - ], - read: [ - "internal_user_mgt_view", - "internal_role_mgt_view", - "internal_group_mgt_view", - "internal_userstore_view" - ], - update: [ - "internal_group_mgt_update" - ], - delete: [ - "internal_group_mgt_delete" - ] - } - }, - identityProviders: { - disabledFeatures: [], - enabled: true, - scopes: { - feature: [ - "console:idps" - ], - create: [ - "internal_idp_create" - ], - read: [ - "internal_userstore_view", - "internal_idp_view", - "internal_role_mgt_view", - "internal_claim_meta_view" - ], - update: [ - "internal_idp_update" - ], - delete: [ - "internal_idp_delete" - ] - } - }, - manageGettingStarted: { - enabled: true, - disabledFeatures: [], - scopes: { - create: [], - read: [ - "internal_user_mgt_create", - "internal_group_mgt_create" - ], - update: [], - delete: [] - } - }, - oidcScopes: { - enabled: true, - disabledFeatures: [], - scopes: { - feature: [ - "console:scopes:oidc" - ], - create: [ - "internal_oidc_scope_mgt_create" - ], - read: [ - "internal_oidc_scope_mgt_view", - "internal_claim_meta_view" - ], - update: [ - "internal_oidc_scope_mgt_update" - ], - delete: [ - "internal_oidc_scope_mgt_delete" - ] - } - }, - secretsManagement: { - enabled: false, - disabledFeatures: [], - scopes: { - create: [ - "internal_secret_mgt_add" - ], - read: [ - "internal_secret_mgt_view" - ], - update: [ - "internal_secret_mgt_update" - ], - delete: [ - "internal_secret_mgt_delete" - ] - } - }, - remoteFetchConfig: { - enabled: true, - disabledFeatures: [], - scopes: { - create: [ - "internal_identity_mgt_view", - "internal_identity_mgt_update", - "internal_identity_mgt_create", - "internal_identity_mgt_delete" - ], - read: [ - "internal_identity_mgt_view", - "internal_identity_mgt_update", - "internal_identity_mgt_create", - "internal_identity_mgt_delete" - ], - update: [ - "internal_identity_mgt_view", - "internal_identity_mgt_update", - "internal_identity_mgt_create", - "internal_identity_mgt_delete" - ], - delete: [ - "internal_identity_mgt_view", - "internal_identity_mgt_update", - "internal_identity_mgt_create", - "internal_identity_mgt_delete" - ] - } - }, - roles: { - enabled: true, - disabledFeatures: [], - scopes: { - feature: [ - "console:roles" - ], - create: [ - "internal_role_mgt_create" - ], - read: [ - "internal_user_mgt_view", - "internal_role_mgt_view", - "internal_userstore_view", - "internal_group_mgt_view" - ], - update: [ - "internal_role_mgt_update" - ], - delete: [ - "internal_role_mgt_delete" - ] + serverOriginWithTenant: "https://api.wso2iam.io/t/testorg", + session: { + sessionRefreshTimeOut: 300, + userIdleWarningTimeOut: 1740, + checkSessionInterval: 5, + userIdleTimeOut: 1800 + }, + superTenant: "carbon.super", + tenant: "testorg", + tenantPath: "/t/testorg", + tenantPrefix: "t", + ui: { + appCopyright: "${copyright} ${year} WSO2, Inc.", + appTitle: "Asgardeo Console", + appName: "Console", + applicationTemplateLoadingStrategy: "LOCAL", + identityProviderTemplateLoadingStrategy: "LOCAL", + appLogoPath: "/assets/images/branding/logo.svg", + showAppSwitchButton: true, + features: { + accountSecurity: { + enabled: true, + disabledFeatures: [], + scopes: { + create: [], + read: [ + "internal_governance_view" + ], + update: [ + "internal_governance_update" + ], + delete: [] + } + }, + accountRecovery: { + enabled: true, + disabledFeatures: [], + scopes: { + create: [], + read: [ + "internal_governance_view" + ], + update: [ + "internal_governance_update" + ], + delete: [] + } + }, + applications: { + disabledFeatures: [], + enabled: true, + scopes: { + feature: [ + "console:applications" + ], + create: [ + "internal_application_mgt_create" + ], + read: [ + "internal_cors_origins_view", + "internal_application_mgt_view", + "internal_claim_meta_view", + "internal_idp_view" + ], + update: [ + "internal_application_mgt_update" + ], + delete: [ + "internal_application_mgt_delete" + ] + } + }, + approvals: { + disabledFeatures: [], + enabled: false, + scopes: { + create: [ + "internal_humantask_view" + ], + read: [ + "internal_humantask_view" + ], + update: [ + "internal_humantask_view" + ], + delete: [ + "internal_humantask_view" + ] + } + }, + attributeDialects: { + enabled: true, + disabledFeatures: [], + scopes: { + feature: [ + "console:attributes" + ], + create: [ + "internal_claim_meta_create" + ], + read: [ + "internal_userstore_view", + "internal_claim_meta_view" + ], + update: [ + "internal_claim_meta_update" + ], + delete: [ + "internal_claim_meta_delete" + ] + } + }, + branding: { + enabled: true, + disabledFeatures: [], + scopes: { + create: [ + "internal_application_mgt_update" + ], + delete: [ + "internal_application_mgt_update" + ], + read: [ + "internal_application_mgt_view" + ], + update: [ + "internal_application_mgt_update" + ] + } + }, + certificates: { + enabled: true, + disabledFeatures: [], + scopes: { + create: [ + "internal_keystore_update" + ], + read: [ + "internal_keystore_view" + ], + update: [ + "internal_keystore_update" + ], + delete: [ + "internal_keystore_update" + ] + } + }, + developerGettingStarted: { + enabled: true, + disabledFeatures: [], + scopes: { + create: [], + read: [ + "internal_application_mgt_create", + "internal_idp_create", + "internal_application_mgt_update" + ], + update: [], + delete: [] + } + }, + emailTemplates: { + enabled: true, + disabledFeatures: [], + scopes: { + create: [ + "internal_email_mgt_create" + ], + read: [ + "internal_email_mgt_view" + ], + update: [ + "internal_email_mgt_update" + ], + delete: [ + "internal_email_mgt_delete" + ] + } + }, + governanceConnectors: { + enabled: true, + disabledFeatures: [], + scopes: { + create: [], + read: [ + "internal_governance_view" + ], + update: [ + "internal_governance_update" + ], + delete: [] + } + }, + groups: { + enabled: true, + disabledFeatures: [], + scopes: { + feature: [ + "console:groups" + ], + create: [ + "internal_group_mgt_create" + ], + read: [ + "internal_user_mgt_view", + "internal_role_mgt_view", + "internal_group_mgt_view", + "internal_userstore_view" + ], + update: [ + "internal_group_mgt_update" + ], + delete: [ + "internal_group_mgt_delete" + ] + } + }, + identityProviders: { + disabledFeatures: [], + enabled: true, + scopes: { + feature: [ + "console:idps" + ], + create: [ + "internal_idp_create" + ], + read: [ + "internal_userstore_view", + "internal_idp_view", + "internal_role_mgt_view", + "internal_claim_meta_view" + ], + update: [ + "internal_idp_update" + ], + delete: [ + "internal_idp_delete" + ] + } + }, + manageGettingStarted: { + enabled: true, + disabledFeatures: [], + scopes: { + create: [], + read: [ + "internal_user_mgt_create", + "internal_group_mgt_create" + ], + update: [], + delete: [] + } + }, + oidcScopes: { + enabled: true, + disabledFeatures: [], + scopes: { + feature: [ + "console:scopes:oidc" + ], + create: [ + "internal_oidc_scope_mgt_create" + ], + read: [ + "internal_oidc_scope_mgt_view", + "internal_claim_meta_view" + ], + update: [ + "internal_oidc_scope_mgt_update" + ], + delete: [ + "internal_oidc_scope_mgt_delete" + ] + } + }, + secretsManagement: { + enabled: false, + disabledFeatures: [], + scopes: { + create: [ + "internal_secret_mgt_add" + ], + read: [ + "internal_secret_mgt_view" + ], + update: [ + "internal_secret_mgt_update" + ], + delete: [ + "internal_secret_mgt_delete" + ] + } + }, + remoteFetchConfig: { + enabled: true, + disabledFeatures: [], + scopes: { + create: [ + "internal_identity_mgt_view", + "internal_identity_mgt_update", + "internal_identity_mgt_create", + "internal_identity_mgt_delete" + ], + read: [ + "internal_identity_mgt_view", + "internal_identity_mgt_update", + "internal_identity_mgt_create", + "internal_identity_mgt_delete" + ], + update: [ + "internal_identity_mgt_view", + "internal_identity_mgt_update", + "internal_identity_mgt_create", + "internal_identity_mgt_delete" + ], + delete: [ + "internal_identity_mgt_view", + "internal_identity_mgt_update", + "internal_identity_mgt_create", + "internal_identity_mgt_delete" + ] + } + }, + roles: { + enabled: true, + disabledFeatures: [], + scopes: { + feature: [ + "console:roles" + ], + create: [ + "internal_role_mgt_create" + ], + read: [ + "internal_user_mgt_view", + "internal_role_mgt_view", + "internal_userstore_view", + "internal_group_mgt_view" + ], + update: [ + "internal_role_mgt_update" + ], + delete: [ + "internal_role_mgt_delete" + ] + } + }, + userOnboarding: { + enabled: true, + disabledFeatures: [], + scopes: { + create: [], + read: [ + "internal_governance_view" + ], + update: [ + "internal_governance_update" + ], + delete: [] + } + }, + userStores: { + enabled: true, + disabledFeatures: [], + scopes: { + create: [ + "internal_userstore_create" + ], + read: [ + "internal_userstore_view" + ], + update: [ + "internal_userstore_update" + ], + delete: [ + "internal_userstore_delete" + ] + } + }, + users: { + enabled: true, + disabledFeatures: [], + scopes: { + feature: [ + "console:users" + ], + create: [ + "internal_user_mgt_create" + ], + read: [ + "internal_userstore_view", + "internal_role_mgt_view", + "internal_group_mgt_view", + "internal_governance_view", + "internal_login", + "internal_user_mgt_view", + "internal_user_mgt_list", + "internal_session_view" + ], + update: [ + "internal_user_mgt_update", + "internal_session_delete" + ], + delete: [ + "internal_user_mgt_delete" + ] + } } }, - userOnboarding: { - enabled: true, - disabledFeatures: [], - scopes: { - create: [], - read: [ - "internal_governance_view" - ], - update: [ - "internal_governance_update" - ], - delete: [] - } + gravatarConfig: { + fallback: "404" }, - userStores: { - enabled: true, - disabledFeatures: [], - scopes: { - create: [ - "internal_userstore_create" - ], - read: [ - "internal_userstore_view" - ], - update: [ - "internal_userstore_update" - ], - delete: [ - "internal_userstore_delete" - ] - } + hiddenAuthenticators: [ + "FIDOAuthenticator", + "IdentifierExecutor" + ], + hiddenConnectionTemplates: [], + i18nConfigs: { + showLanguageSwitcher: false, + langAutoDetectEnabled: false }, - users: { - enabled: true, - disabledFeatures: [], - scopes: { - feature: [ - "console:users" - ], - create: [ - "internal_user_mgt_create" - ], - read: [ - "internal_userstore_view", - "internal_role_mgt_view", - "internal_group_mgt_view", - "internal_governance_view", - "internal_login", - "internal_user_mgt_view", - "internal_user_mgt_list", - "internal_session_view" - ], - update: [ - "internal_user_mgt_update", - "internal_session_delete" - ], - delete: [ - "internal_user_mgt_delete" - ] + identityProviderTemplates: { + enterpriseOIDC: { + enabled: true + }, + enterpriseSAML: { + enabled: true + }, + facebook: { + enabled: true + }, + google: { + enabled: true + }, + github: { + enabled: true + }, + microsoft: { + enabled: true } - } - }, - gravatarConfig: { - fallback: "404" - }, - hiddenAuthenticators: [ - "FIDOAuthenticator", - "IdentifierExecutor" - ], - hiddenConnectionTemplates: [], - i18nConfigs: { - showLanguageSwitcher: false, - langAutoDetectEnabled: false - }, - identityProviderTemplates: { - enterpriseOIDC: { - enabled: true - }, - enterpriseSAML: { - enabled: true }, - facebook: { - enabled: true + isCookieConsentBannerEnabled: true, + isGroupAndRoleSeparationEnabled: true, + isSignatureValidationCertificateAliasEnabled: false, + isCustomClaimMappingEnabled: true, + isCustomClaimMappingMergeEnabled: true, + isClientSecretHashEnabled: false, + isDefaultDialectEditingEnabled: false, + isDialectAddingEnabled: false, + isHeaderAvatarLabelAllowed: false, + isLeftNavigationCategorized: false, + isRequestPathAuthenticationEnabled: false, + listAllAttributeDialects: false, + privacyPolicyConfigs: { + visibleOnFooter: false }, - google: { - enabled: true + productName: "Asgardeo", + productVersionConfig: { + productVersion: "BETA" }, - github: { - enabled: true - }, - microsoft: { - enabled: true + selfAppIdentifier: "Console", + systemAppsIdentifiers: [ + "Console", + "My Account" + ], + theme: { + name: "asgardio" } - }, - isCookieConsentBannerEnabled: true, - isGroupAndRoleSeparationEnabled: true, - isSignatureValidationCertificateAliasEnabled: false, - isCustomClaimMappingEnabled: true, - isCustomClaimMappingMergeEnabled: true, - isClientSecretHashEnabled: false, - isDefaultDialectEditingEnabled: false, - isDialectAddingEnabled: false, - isHeaderAvatarLabelAllowed: false, - isLeftNavigationCategorized: false, - isRequestPathAuthenticationEnabled: false, - listAllAttributeDialects: false, - privacyPolicyConfigs: { - visibleOnFooter: false - }, - productName: "Asgardeo", - productVersionConfig: { - productVersion: "BETA" - }, - selfAppIdentifier: "Console", - systemAppsIdentifiers: [ - "Console", - "My Account" - ], - theme: { - name: "asgardio" } - } - }; - } - /* eslint-disable sort-keys, max-len */ -}; + }; + } + /* eslint-disable sort-keys, max-len */ + }; +} diff --git a/features/jest.config.ts b/features/jest.config.ts index ccb4c41934b..7a52746f8bd 100644 --- a/features/jest.config.ts +++ b/features/jest.config.ts @@ -30,6 +30,7 @@ module.exports = { "json", "node" ], + /* eslint-disable sort-keys */ moduleNameMapper: { "@oxygen-ui/react": "/node_modules/@oxygen-ui/react", "@wso2is/core/api": "/../modules/core/dist/src/api", @@ -44,8 +45,8 @@ module.exports = { "@wso2is/core/utils": "/../modules/core/dist/src/utils", "@wso2is/core/workers": "/../modules/core/dist/src/workers", "@wso2is/dynamic-forms": "/../modules/dynamic-forms/dist", - "@wso2is/form": "/../modules/form/dist", "@wso2is/forms": "/../modules/forms/dist", + "@wso2is/form": "/../modules/form/dist", "@wso2is/react-components": "/../modules/react-components/dist", "\\.(css|less|scss)$": "/../modules/unit-testing/__mocks__/style-file.ts", "\\.(jpg|jpeg|png|gif|eot|otf|webp|ttf|woff|woff2|mp4|webm|wav|mp3|m4a|aac|oga|md)$": diff --git a/modules/unit-testing/__mocks__/global.ts b/modules/unit-testing/__mocks__/global.ts index 17073a673d4..c92a964a972 100644 --- a/modules/unit-testing/__mocks__/global.ts +++ b/modules/unit-testing/__mocks__/global.ts @@ -52,5 +52,5 @@ global.console = { debug: jest.fn(), error: jest.fn(), info: jest.fn(), - log: jest.fn() + warn: jest.fn() }; diff --git a/modules/unit-testing/__mocks__/redux/redux-store-state.ts b/modules/unit-testing/__mocks__/redux/redux-store-state.ts index 1dedc486b5a..676560a3e85 100644 --- a/modules/unit-testing/__mocks__/redux/redux-store-state.ts +++ b/modules/unit-testing/__mocks__/redux/redux-store-state.ts @@ -16,6 +16,8 @@ * under the License. */ +import { AppState } from "@wso2is/admin.core.v1"; + /** * Mocks the Redux store state. * @@ -23,18 +25,214 @@ */ /* eslint-disable sort-keys, max-len, @typescript-eslint/typedef */ -const ReduxStoreStateMock: any = { +const ReduxStoreStateMock: AppState = { accessControl: { isDevelopAllowed: true, isManageAllowed: true }, application: { + groupedTemplates: [ + { + id: "6a90e4b0-fbff-42d7-bfde-1efd98f07cd7", + templateId: "single-page-application", + name: "Single-Page Application", + description: "A web application that runs application logic in the browser.", + image: "spa", + authenticationProtocol: "oidc", + types: [ + { + displayName: "React", + logo: {}, + name: "react" + }, + { + displayName: "Angular", + logo: {}, + name: "angular" + }, + { + displayName: "Vue", + logo: {}, + name: "vue" + }, + { + displayName: "Javascript", + logo: {}, + name: "javascript" + } + ], + category: "DEFAULT", + displayOrder: 0, + templateGroup: "spa", + application: { + name: "", + advancedConfigurations: { + discoverableByEndUsers: false, + skipLogoutConsent: true, + skipLoginConsent: true + }, + authenticationSequence: { + type: "DEFAULT", + steps: [ + { + id: 1, + options: [ + { + idp: "LOCAL", + authenticator: "basic" + } + ] + } + ] + }, + claimConfiguration: { + dialect: "LOCAL", + requestedClaims: [ + { + claim: { + uri: "http://wso2.org/claims/username" + } + } + ] + }, + inboundProtocolConfiguration: { + oidc: { + accessToken: { + applicationAccessTokenExpiryInSeconds: 3600, + bindingType: "sso-session", + revokeTokensWhenIDPSessionTerminated: true, + type: "Default", + userAccessTokenExpiryInSeconds: 3600, + validateTokenBinding: false + }, + grantTypes: [ + "authorization_code", + "refresh_token" + ], + allowedOrigins: [ + "https://localhost:3000" + ], + callbackURLs: [ + "https://localhost:3000" + ], + pkce: { + mandatory: true, + supportPlainTransformAlgorithm: false + }, + publicClient: true, + refreshToken: { + expiryInSeconds: 86400, + renewRefreshToken: true + } + } + } + } + } + ], meta: { customInboundProtocolChecked: false, customInboundProtocols: [], inboundProtocols: [], protocolMeta: {} - } + }, + templates: [ + { + id: "6a90e4b0-fbff-42d7-bfde-1efd98f07cd7", + templateId: "single-page-application", + name: "Single-Page Application", + description: "A web application that runs application logic in the browser.", + image: "spa", + authenticationProtocol: "oidc", + types: [ + { + displayName: "React", + logo: {}, + name: "react" + }, + { + displayName: "Angular", + logo: {}, + name: "angular" + }, + { + displayName: "Vue", + logo: {}, + name: "vue" + }, + { + displayName: "Javascript", + logo: {}, + name: "javascript" + } + ], + category: "DEFAULT", + displayOrder: 0, + templateGroup: "spa", + application: { + name: "", + advancedConfigurations: { + discoverableByEndUsers: false, + skipLogoutConsent: true, + skipLoginConsent: true + }, + authenticationSequence: { + type: "DEFAULT", + steps: [ + { + id: 1, + options: [ + { + idp: "LOCAL", + authenticator: "basic" + } + ] + } + ] + }, + claimConfiguration: { + dialect: "LOCAL", + requestedClaims: [ + { + claim: { + uri: "http://wso2.org/claims/username" + } + } + ] + }, + inboundProtocolConfiguration: { + oidc: { + accessToken: { + applicationAccessTokenExpiryInSeconds: 3600, + bindingType: "sso-session", + revokeTokensWhenIDPSessionTerminated: true, + type: "Default", + userAccessTokenExpiryInSeconds: 3600, + validateTokenBinding: false + }, + grantTypes: [ + "authorization_code", + "refresh_token" + ], + allowedOrigins: [ + "https://localhost:3000" + ], + callbackURLs: [ + "https://localhost:3000" + ], + pkce: { + mandatory: true, + supportPlainTransformAlgorithm: false + }, + publicClient: true, + refreshToken: { + expiryInSeconds: 86400, + renewRefreshToken: true + } + } + } + } + } + ] }, auth: { displayName: "admin@carbon.super", @@ -161,6 +359,27 @@ const ReduxStoreStateMock: any = { identityProviderTemplateLoadingStrategy: "LOCAL", appTitle: "Console | WSO2 Identity Server", features: { + apiResources: { + disabledFeatures: [], + enabled: true, + scopes: { + create: [ + "internal_api_resource_create" + ], + delete: [ + "internal_api_resource_delete" + ], + feature: [ + "console:apiResources" + ], + read: [ + "internal_api_resource_view" + ], + update: [ + "internal_api_resource_update" + ] + } + }, applications: { disabledFeatures: [], enabled: true, @@ -442,6 +661,23 @@ const ReduxStoreStateMock: any = { isLeftNavigationCategorized: true, isRequestPathAuthenticationEnabled: true, isSignatureValidationCertificateAliasEnabled: false, + legacyMode: { + apiResourcesV1: false, + apiResourcesV2: true, + applicationListSystemApps: false, + applicationOIDCSubjectIdentifier: true, + applicationRequestPathAuthentication: false, + applicationSystemAppsSettings: false, + backupCodesForSubOrganizations: true, + certificates: false, + consoleFeatureScopeCheck: true, + loginAndRegistrationEmailDomainDiscovery: true, + organizations: true, + roleMapping: false, + rolesV1: false, + saasApplications: false, + secretsManagement: false + }, listAllAttributeDialects: false, privacyPolicyConfigs: { visibleOnFooter: true @@ -569,7 +805,9 @@ const ReduxStoreStateMock: any = { name: "Organization", id: "org-0001", ref: "/org-001" - } + }, + isFirstLevelOrganization: true, + organizationType: "SUPER_ORGANIZATION" }, profile: { isSCIMEnabled: true, diff --git a/modules/unit-testing/__mocks__/window/app-utils.ts b/modules/unit-testing/__mocks__/window/app-utils.ts index 1a8b295e198..bf003da854f 100644 --- a/modules/unit-testing/__mocks__/window/app-utils.ts +++ b/modules/unit-testing/__mocks__/window/app-utils.ts @@ -474,6 +474,23 @@ interface CustomWindow extends Window { isCookieConsentBannerEnabled: false, isGroupAndRoleSeparationEnabled: true, isSignatureValidationCertificateAliasEnabled: false, + legacyMode: { + apiResourcesV1: false, + apiResourcesV2: true, + applicationListSystemApps: false, + applicationOIDCSubjectIdentifier: true, + applicationRequestPathAuthentication: false, + applicationSystemAppsSettings: false, + backupCodesForSubOrganizations: true, + certificates: false, + consoleFeatureScopeCheck: true, + loginAndRegistrationEmailDomainDiscovery: true, + organizations: true, + roleMapping: false, + rolesV1: false, + saasApplications: false, + secretsManagement: false + }, isClientSecretHashEnabled: false, isDefaultDialectEditingEnabled: false, isDialectAddingEnabled: true, diff --git a/modules/unit-testing/utils.tsx b/modules/unit-testing/utils.tsx index 4868cdcebd0..0910d7a0a9a 100644 --- a/modules/unit-testing/utils.tsx +++ b/modules/unit-testing/utils.tsx @@ -19,6 +19,8 @@ // import { AuthProvider } from "@asgardeo/auth-react"; import { RenderResult, render as rtlRender } from "@testing-library/react"; import { AccessControlProvider } from "@wso2is/access-control"; +import { AppConfigProvider } from "@wso2is/admin.core.v1/providers/app-config-provider"; +import GlobalVariablesProvider from "@wso2is/admin.core.v1/providers/global-variables-provider"; import UIConfigProvider from "@wso2is/admin.core.v1/providers/ui-config-provider"; import React, { PropsWithChildren, ReactElement } from "react"; import { Provider } from "react-redux"; @@ -61,18 +63,27 @@ const render = ( // fallback={ } // getAuthParams={ AuthenticateUtils.getAuthParams } // > - - - - { children } - - - - // + + + + + + { children } + + + + + + // ); };