From 97f3f90fc52d6a3e7f767231c1d1e4c77494be84 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ivan=20Vrbov=C4=8Dan?= Date: Wed, 4 Oct 2023 11:46:41 +0200 Subject: [PATCH] Defining endpoint security through JDL This commit introduces a role-based security feature for the backend, allowing users to define security configurations using JDL clauses. Security can now be specified directly within JDL for various entities, where different roles with specified permissions are mapped to the corresponding Java entity resource files. This mapping auto-generates the requisite `@Secured({})` annotations on the endpoints, ensuring the right level of access control based on roles. Examples of the JDL security clauses include: ``` secure entity1, entity2, entity3 with roles { ROLE_ADMIN allows (get, post, put, delete) ROLE_USER allows (get) } ``` and ``` secure all except entity3 with roles { ROLE_ADMIN allows (get, post, put, delete) ROLE_USER allows (get) } ``` which would translate to `@Secured({ "ROLE_ADMIN" })` annotations in the generated Java code for specified endpoints. Additionally, the integration tests have been updated to consider these role-based security configurations. Moreover, the groundwork for supporting other security types has been laid down through the inclusion of grammar and parser definitions. These additional security types encompass privilege security, organizational security, parent based security, and relation based security, although the code generators for these types are not included in this commit. Resolves #19201 --- .../support/prepare-entity.mts | 208 +- .../support/prepare-entity.spec.mts | 161 ++ .../bootstrap-application/generator.spec.mts | 18 + generators/server/entity-files.mjs | 20 +- .../web/rest/_entityClass_Resource.java.ejs | 53 + .../_entityClass_secRoles_ResourceIT.java.ejs | 2318 +++++++++++++++++ jdl/__test-files__/security1.jdl | 6 + jdl/__test-files__/security2.jdl | 6 + jdl/__test-files__/security3.jdl | 7 + .../jdl-to-json-secure-converter.ts | 60 + ...ith-applications-to-json-converter.spec.ts | 34 +- ...jdl-with-applications-to-json-converter.ts | 12 + ...hout-application-to-json-converter.spec.ts | 32 + ...l-without-application-to-json-converter.ts | 14 + .../option-converter.spec.ts.snap | 14 + .../entity-converter.spec.ts | 81 + .../entity-converter.ts | 9 +- ...parsed-jdl-to-jdl-object-converter.spec.ts | 55 + .../parsed-jdl-to-jdl-object-converter.ts | 31 + jdl/jdl-importer.spec.ts | 98 + jdl/jhipster/index.mts | 1 + jdl/jhipster/json-entity.spec.ts | 113 + jdl/jhipster/json-entity.ts | 13 + jdl/jhipster/json-secure.spec.ts | 145 ++ jdl/jhipster/json-secure.ts | 132 + jdl/jhipster/reserved-keywords/jhipster.ts | 1 + jdl/jhipster/unary-options.spec.ts | 1 + jdl/jhipster/unary-options.ts | 1 + jdl/models/index.mts | 1 + jdl/models/jdl-entity.ts | 12 + jdl/models/jdl-secure.spec.ts | 145 ++ jdl/models/jdl-secure.ts | 139 + jdl/models/jdl-security-type.ts | 22 + jdl/parsing/dsl-api.spec.ts | 3 +- jdl/parsing/grammar.spec.ts | 30 + jdl/parsing/jdl-ast-builder-visitor.ts | 165 ++ jdl/parsing/jdl-parser.ts | 179 ++ jdl/parsing/lexer/lexer.ts | 15 + jdl/parsing/validator.ts | 12 + jdl/utils/object-utils.ts | 61 +- .../jdl-entities/app-roles-security.jdl | 195 ++ 41 files changed, 4615 insertions(+), 8 deletions(-) create mode 100644 generators/server/templates/src/test/java/_package_/_entityPackage_/web/rest/_entityClass_secRoles_ResourceIT.java.ejs create mode 100644 jdl/__test-files__/security1.jdl create mode 100644 jdl/__test-files__/security2.jdl create mode 100644 jdl/__test-files__/security3.jdl create mode 100644 jdl/converters/jdl-to-json/jdl-to-json-secure-converter.ts create mode 100644 jdl/jhipster/json-secure.spec.ts create mode 100644 jdl/jhipster/json-secure.ts create mode 100644 jdl/models/jdl-secure.spec.ts create mode 100644 jdl/models/jdl-secure.ts create mode 100644 jdl/models/jdl-security-type.ts create mode 100644 test-integration/samples/jdl-entities/app-roles-security.jdl diff --git a/generators/base-application/support/prepare-entity.mts b/generators/base-application/support/prepare-entity.mts index da6b117a8d4c..51f4bcc1e501 100644 --- a/generators/base-application/support/prepare-entity.mts +++ b/generators/base-application/support/prepare-entity.mts @@ -21,9 +21,9 @@ import pluralize from 'pluralize'; import type BaseGenerator from '../../base-core/index.mjs'; import { getDatabaseTypeData, hibernateSnakeCase } from '../../server/support/index.mjs'; -import { createFaker, parseChangelog, stringHashCode, upperFirstCamelCase, getMicroserviceAppName } from '../../base/support/index.mjs'; +import { createFaker, getMicroserviceAppName, parseChangelog, stringHashCode, upperFirstCamelCase } from '../../base/support/index.mjs'; import { fieldToReference } from './prepare-field.mjs'; -import { getTypescriptKeyType, getEntityParentPathAddition } from '../../client/support/index.mjs'; +import { getEntityParentPathAddition, getTypescriptKeyType } from '../../client/support/index.mjs'; import { applicationTypes, authenticationTypes, @@ -36,6 +36,7 @@ import { import { fieldIsEnum } from './field-utils.mjs'; import { Entity } from '../types/index.mjs'; +import { JDLSecurityType } from '../../../jdl/models/jdl-security-type.js'; const { sortedUniq, intersection } = _; @@ -260,10 +261,213 @@ export default function prepareEntity(entityWithConfig, generator, application) return Object.fromEntries(fieldEntries); }; _derivedProperties(entityWithConfig); + defineEntitySecurity(entityWithConfig); return entityWithConfig; } +/** + * Resets the security settings for the given entity. + * + * @param {any} entityWithConfig - The entity with its security configuration. + * @return {void} - Returns nothing. + */ +function resetSecurity(entityWithConfig: any) { + entityWithConfig.hasSecurity = false; + entityWithConfig.hasRolesSecurity = false; + entityWithConfig.hasOrganizationalSecurity = false; + entityWithConfig.hasPrivilegeSecurity = false; + entityWithConfig.hasRelationSecurity = false; + entityWithConfig.hasParentSecurity = false; +} + +/** + * Defines role based security for an entity. + * + * @param {any} entityWithConfig - The entity with security configuration. + */ +function defineRoleBasedSecurity(entityWithConfig: any) { + entityWithConfig.hasSecurity = true; + entityWithConfig.hasRolesSecurity = true; + const allRoles: any[] = []; + const allowedRolesAsString: any = { get: [], put: [], post: [], delete: [] }; + const allowedRoles: any = { get: [], put: [], post: [], delete: [] }; + for (let i = 0; i < entityWithConfig.secure.roles.length; i++) { + const role = `"${entityWithConfig.secure.roles[i].role}"`; + allRoles.push(entityWithConfig.secure.roles[i].role); + for (let j = 0; j < entityWithConfig.secure.roles[i].actionList.length; j++) { + const action = _.lowerCase(entityWithConfig.secure.roles[i].actionList[j]); + if (!allowedRolesAsString[action].includes(role)) { + allowedRolesAsString[action].push(role); + allowedRoles[action].push(entityWithConfig.secure.roles[i].role); + } + } + } + const forbiddenRoles: any = {}; + forbiddenRoles.get = allRoles.filter(item => allowedRoles.get.indexOf(item) < 0); + forbiddenRoles.put = allRoles.filter(item => allowedRoles.put.indexOf(item) < 0); + forbiddenRoles.post = allRoles.filter(item => allowedRoles.post.indexOf(item) < 0); + forbiddenRoles.delete = allRoles.filter(item => allowedRoles.delete.indexOf(item) < 0); + entityWithConfig.security.roles = { + get: allowedRolesAsString.get.length > 0 ? allowedRolesAsString.get.join(', ') : '"DUMMY_ROLE_NO_ACCESS"', + put: allowedRolesAsString.put.length > 0 ? allowedRolesAsString.put.join(', ') : '"DUMMY_ROLE_NO_ACCESS"', + post: allowedRolesAsString.post.length > 0 ? allowedRolesAsString.post.join(', ') : '"DUMMY_ROLE_NO_ACCESS"', + delete: allowedRolesAsString.delete.length > 0 ? allowedRolesAsString.delete.join(', ') : '"DUMMY_ROLE_NO_ACCESS"', + }; + entityWithConfig.security.allowedRoles = { + get: [...allowedRoles.get], + put: [...allowedRoles.put], + post: [...allowedRoles.post], + delete: [...allowedRoles.delete], + }; + entityWithConfig.security.forbiddenRoles = { + get: [...forbiddenRoles.get], + put: [...forbiddenRoles.put], + post: [...forbiddenRoles.post], + delete: [...forbiddenRoles.delete], + }; +} + +/** + * Defines privilege-based security for an entity with configuration. + * + * @param {Object} entityWithConfig - The entity with configuration. + * @return {void} + */ +function definePrivilegeBasedSecurity(entityWithConfig: any) { + entityWithConfig.hasSecurity = true; + entityWithConfig.hasPrivilegeSecurity = true; + const privileges = {}; + for (let i = 0; i < entityWithConfig.secure.privileges.length; i++) { + const action = _.lowerCase(entityWithConfig.secure.privileges[i].action); + if (entityWithConfig.secure.privileges[i].privList.length > 0) { + privileges[action] = entityWithConfig.secure.privileges[i].privList + .map(x => `"${x}"`) + .join(', ') + .toUpperCase(); + } + } + entityWithConfig.security.privileges = privileges; +} + +/** + * Sets the organizational security for the given entity with configuration. + * + * @param {any} entityWithConfig - The entity object with configuration. + * @return {void} + */ +function defineOrganizationalSecurity(entityWithConfig: any) { + entityWithConfig.hasSecurity = true; + entityWithConfig.hasOrganizationalSecurity = true; + entityWithConfig.security.organizationalSecurity = entityWithConfig.secure.organizationalSecurity; +} + +/** + * Defines parent based security for the given entity. + * + * @param {any} entityWithConfig - The entity object with configuration. + */ +function defineParentBasedSecurity(entityWithConfig: any) { + entityWithConfig.hasSecurity = true; + entityWithConfig.hasParentSecurity = true; + const parentPrivileges = entityWithConfig.secure.parentPrivileges; + entityWithConfig.security.parentPrivileges = parentPrivileges; + entityWithConfig.security.roles = { + get: 'ROLE_ADMIN', + put: 'ROLE_ADMIN', + post: 'ROLE_ADMIN', + delete: 'ROLE_ADMIN', + }; + entityWithConfig.security.parentPrivileges.parentEntityClass = _.upperFirst(parentPrivileges.parent); + entityWithConfig.security.parentPrivileges.parentEntityName = parentPrivileges.parent; + entityWithConfig.security.parentPrivileges.parentEntityApiUrl = _.kebabCase(_.lowerFirst(pluralize(parentPrivileges.parent))); + entityWithConfig.security.parentPrivileges.parentEntityInstance = _.lowerFirst(parentPrivileges.parent); + entityWithConfig.security.parentPrivileges.parentInstanceName = _.lowerFirst(parentPrivileges.parent); + entityWithConfig.security.parentPrivileges.parentFieldNameUpper = _.upperFirst(parentPrivileges.field); + entityWithConfig.security.parentPrivileges.parentFieldName = parentPrivileges.field; +} + +/** + * Sets up relational security for the given entity with configuration. + * + * @param {any} entityWithConfig - The entity with its security configuration. + * @return {void} + */ +function defineRelationalSecurity(entityWithConfig: any) { + entityWithConfig.hasSecurity = true; + entityWithConfig.hasRelationSecurity = true; + entityWithConfig.security.relPrivileges = entityWithConfig.secure.relPrivileges; +} + +/** + * Retrieves the security type associated with the given entity configuration. + * @param {any} entityWithConfig - The entity with its configuration. + * @return {JDLSecurityType} - The security type of the entity. Returns JDLSecurityType.None if no security type is defined. + */ +function getSecurityType(entityWithConfig: any): JDLSecurityType { + return entityWithConfig.secure?.securityType || JDLSecurityType.None; +} + +/** + * Checks if the given security type is of "None" type. + * + * @param {JDLSecurityType} securityType - The security type to be checked. + * @return {boolean} - True if the security type is "None", false otherwise. + */ +function isNoneSecurityType(securityType: JDLSecurityType): boolean { + return securityType === JDLSecurityType.None; +} + +/** + * Assigns a security type to an entity object and initialize security object on the entity. + * + * @param {any} entityWithConfig - The entity object with configuration. + * @param {JDLSecurityType} securityType - The security type to assign. + * @return {void} + */ +function assignEntitySecurity(entityWithConfig: any, securityType: JDLSecurityType): void { + entityWithConfig.security = { + securityType, + }; +} + +/** + * Defines the security for the given entity. Security is defined through 'secure' property in the entity configuration. + * + * @param {Object} entityWithConfig - The entity with its configuration. + * @throws {Error} If the entity has an unknown security type. + */ +function defineEntitySecurity(entityWithConfig: any): void { + const securityType = getSecurityType(entityWithConfig); + + if (isNoneSecurityType(securityType)) { + resetSecurity(entityWithConfig); + return; + } + + assignEntitySecurity(entityWithConfig, securityType); + + switch (securityType) { + case JDLSecurityType.Roles: + defineRoleBasedSecurity(entityWithConfig); + break; + case JDLSecurityType.Privileges: + definePrivilegeBasedSecurity(entityWithConfig); + break; + case JDLSecurityType.OrganizationalSecurity: + defineOrganizationalSecurity(entityWithConfig); + break; + case JDLSecurityType.ParentPrivileges: + defineParentBasedSecurity(entityWithConfig); + break; + case JDLSecurityType.RelPrivileges: + defineRelationalSecurity(entityWithConfig); + break; + default: + throw new Error(`Entity ${entityWithConfig.name()} has defined unknown security type: ${securityType}`); + } +} + export function derivedPrimaryKeyProperties(primaryKey) { _.defaults(primaryKey, { hasUUID: primaryKey.fields && primaryKey.fields.some(field => field.fieldType === UUID), diff --git a/generators/base-application/support/prepare-entity.spec.mts b/generators/base-application/support/prepare-entity.spec.mts index 8328f9901d6a..38bdb0488daf 100644 --- a/generators/base-application/support/prepare-entity.spec.mts +++ b/generators/base-application/support/prepare-entity.spec.mts @@ -22,6 +22,8 @@ import { formatDateForChangelog } from '../../base/support/index.mjs'; import { prepareEntityPrimaryKeyForTemplates, entityDefaultConfig } from './prepare-entity.mjs'; import BaseGenerator from '../../base/index.mjs'; import { getConfigWithDefaults } from '../../../jdl/jhipster/index.mjs'; +import { prepareEntity } from './index.mjs'; +import { JDLSecurityType, PrivilegeActionType, RoleActionType } from '../../../jdl/models/jdl-security-type.js'; describe('generator - base-application - support - prepareEntity', () => { const defaultGenerator = { jhipsterConfig: getConfigWithDefaults() }; @@ -299,4 +301,163 @@ describe('generator - base-application - support - prepareEntity', () => { }); }); }); + + describe('prepare entity security', () => { + describe('with no security', () => { + it('prepareEntity should not add security', () => { + const entity = { + ...entityDefaultConfig, + name: 'Entity', + changelogDate: formatDateForChangelog(new Date()), + fields: [{ fieldName: 'id', fieldType: 'CustomType', path: ['id'], relationshipsPath: [] }], + }; + const result = prepareEntity(entity, defaultGenerator, 'testApp'); + expect(result.security).to.be.undefined; + expect(result.hasSecurity).to.equal(false); + expect(result.hasRolesSecurity).to.equal(false); + expect(result.hasOrganizationalSecurity).to.equal(false); + expect(result.hasPrivilegeSecurity).to.equal(false); + expect(result.hasRelationSecurity).to.equal(false); + expect(result.hasParentSecurity).to.equal(false); + }); + + it('prepareEntity should add role based security', () => { + const entity = { + ...entityDefaultConfig, + name: 'Entity', + changelogDate: formatDateForChangelog(new Date()), + fields: [{ fieldName: 'id', fieldType: 'CustomType', path: ['id'], relationshipsPath: [] }], + secure: { + securityType: JDLSecurityType.Roles, + roles: [ + { role: 'ROLE_ALLOWED', actionList: [RoleActionType.Post, RoleActionType.Put, RoleActionType.Get] }, + { role: 'ROLE_FORBIDDEN', actionList: [RoleActionType.Get] }, + ], + }, + }; + const result = prepareEntity(entity, defaultGenerator, 'testApp'); + expect(result.security).to.exist; + expect(result.hasSecurity).to.equal(true); + expect(result.hasRolesSecurity).to.equal(true); + expect(result.security.securityType).to.equal(JDLSecurityType.Roles); + expect(result.security.roles).to.deep.equal({ + get: '"ROLE_ALLOWED", "ROLE_FORBIDDEN"', + put: '"ROLE_ALLOWED"', + post: '"ROLE_ALLOWED"', + delete: '"DUMMY_ROLE_NO_ACCESS"', + }); + expect(result.security.allowedRoles).to.exist; + expect(result.security.allowedRoles).to.deep.equal({ + delete: [], + get: ['ROLE_ALLOWED', 'ROLE_FORBIDDEN'], + post: ['ROLE_ALLOWED'], + put: ['ROLE_ALLOWED'], + }); + expect(result.security.forbiddenRoles).to.exist; + expect(result.security.forbiddenRoles).to.deep.equal({ + delete: ['ROLE_ALLOWED', 'ROLE_FORBIDDEN'], + get: [], + post: ['ROLE_FORBIDDEN'], + put: ['ROLE_FORBIDDEN'], + }); + }); + + it('prepareEntity should add privilege based security', () => { + const entity = { + ...entityDefaultConfig, + name: 'Entity', + changelogDate: formatDateForChangelog(new Date()), + fields: [{ fieldName: 'id', fieldType: 'CustomType', path: ['id'], relationshipsPath: [] }], + secure: { + securityType: JDLSecurityType.Privileges, + privileges: [{ action: PrivilegeActionType.Read, privList: ['Admin'] }], + }, + }; + const result = prepareEntity(entity, defaultGenerator, 'testApp'); + expect(result.security).to.exist; + expect(result.hasSecurity).to.equal(true); + expect(result.hasPrivilegeSecurity).to.equal(true); + expect(result.security.securityType).to.equal(JDLSecurityType.Privileges); + expect(result.security.privileges).to.deep.equal({ read: '"ADMIN"' }); + }); + + it('prepareEntity should add organizational security', () => { + const entity = { + ...entityDefaultConfig, + name: 'Entity', + changelogDate: formatDateForChangelog(new Date()), + fields: [{ fieldName: 'id', fieldType: 'CustomType', path: ['id'], relationshipsPath: [] }], + secure: { + securityType: JDLSecurityType.OrganizationalSecurity, + organizationalSecurity: { resource: 'TestResource' }, + }, + }; + const result = prepareEntity(entity, defaultGenerator, 'testApp'); + expect(result.security).to.exist; + expect(result.hasSecurity).to.equal(true); + expect(result.hasOrganizationalSecurity).to.equal(true); + expect(result.security.securityType).to.equal(JDLSecurityType.OrganizationalSecurity); + expect(result.security.organizationalSecurity).to.deep.equal({ resource: 'TestResource' }); + }); + + it('prepareEntity should add parent based security', () => { + const entity = { + ...entityDefaultConfig, + name: 'Entity', + changelogDate: formatDateForChangelog(new Date()), + fields: [{ fieldName: 'id', fieldType: 'CustomType', path: ['id'], relationshipsPath: [] }], + secure: { + securityType: JDLSecurityType.ParentPrivileges, + parentPrivileges: { parent: 'TestParent', field: 'TestField' }, + }, + }; + const result = prepareEntity(entity, defaultGenerator, 'testApp'); + expect(result.security).to.exist; + expect(result.hasSecurity).to.equal(true); + expect(result.hasParentSecurity).to.equal(true); + expect(result.security.securityType).to.equal(JDLSecurityType.ParentPrivileges); + expect(result.security.parentPrivileges).to.deep.equal({ + field: 'TestField', + parent: 'TestParent', + parentEntityApiUrl: 'test-parents', + parentEntityClass: 'TestParent', + parentEntityInstance: 'testParent', + parentEntityName: 'TestParent', + parentFieldName: 'TestField', + parentFieldNameUpper: 'TestField', + parentInstanceName: 'testParent', + }); + }); + + it('prepareEntity should add relational based security', () => { + const entity = { + ...entityDefaultConfig, + name: 'Entity', + changelogDate: formatDateForChangelog(new Date()), + fields: [{ fieldName: 'id', fieldType: 'CustomType', path: ['id'], relationshipsPath: [] }], + secure: { + securityType: JDLSecurityType.RelPrivileges, + relPrivileges: { + fromEntity: 'FromEntity', + fromField: 'FromField', + toEntity: 'ToEntity', + toField: 'toField', + }, + comment: 'comment', + }, + }; + const result = prepareEntity(entity, defaultGenerator, 'testApp'); + expect(result.security).to.exist; + expect(result.hasSecurity).to.equal(true); + expect(result.hasRelationSecurity).to.equal(true); + expect(result.security.securityType).to.equal(JDLSecurityType.RelPrivileges); + expect(result.security.relPrivileges).to.deep.equal({ + fromEntity: 'FromEntity', + fromField: 'FromField', + toEntity: 'ToEntity', + toField: 'toField', + }); + }); + }); + }); }); diff --git a/generators/bootstrap-application/generator.spec.mts b/generators/bootstrap-application/generator.spec.mts index 5ed36c8831a8..8dd969729dde 100644 --- a/generators/bootstrap-application/generator.spec.mts +++ b/generators/bootstrap-application/generator.spec.mts @@ -548,6 +548,12 @@ describe(`generator - ${generator}`, () => { "fluentMethods": true, "frontendAppName": "jhipsterApp", "generateFakeData": Any, + "hasOrganizationalSecurity": false, + "hasParentSecurity": false, + "hasPrivilegeSecurity": false, + "hasRelationSecurity": false, + "hasRolesSecurity": false, + "hasSecurity": false, "i18nAlertHeaderPrefix": "jhipsterApp.user", "i18nKeyPrefix": "jhipsterApp.user", "implementsEagerLoadApis": false, @@ -812,6 +818,12 @@ describe(`generator - ${generator}`, () => { "fluentMethods": true, "frontendAppName": "jhipsterApp", "generateFakeData": Any, + "hasOrganizationalSecurity": false, + "hasParentSecurity": false, + "hasPrivilegeSecurity": false, + "hasRelationSecurity": false, + "hasRolesSecurity": false, + "hasSecurity": false, "i18nAlertHeaderPrefix": "jhipsterApp.entityA", "i18nKeyPrefix": "jhipsterApp.entityA", "implementsEagerLoadApis": false, @@ -1128,6 +1140,12 @@ describe(`generator - ${generator}`, () => { "fluentMethods": true, "frontendAppName": "jhipsterApp", "generateFakeData": Any, + "hasOrganizationalSecurity": false, + "hasParentSecurity": false, + "hasPrivilegeSecurity": false, + "hasRelationSecurity": false, + "hasRolesSecurity": false, + "hasSecurity": false, "i18nAlertHeaderPrefix": "jhipsterApp.entityA", "i18nKeyPrefix": "jhipsterApp.entityA", "implementsEagerLoadApis": false, diff --git a/generators/server/entity-files.mjs b/generators/server/entity-files.mjs index f1585010304c..8061288bd13e 100644 --- a/generators/server/entity-files.mjs +++ b/generators/server/entity-files.mjs @@ -68,7 +68,7 @@ export const restFiles = { ], restTestFiles: [ { - condition: generator => !generator.embedded, + condition: generator => !generator.embedded && !generator.hasSecurity, path: SERVER_TEST_SRC_DIR, templates: [ { @@ -85,6 +85,24 @@ export const restFiles = { }, ], }, + { + condition: generator => !generator.embedded && generator.hasRolesSecurity, + path: SERVER_TEST_SRC_DIR, + templates: [ + { + file: '_package_/_entityPackage_/web/rest/_entityClass_secRoles_ResourceIT.java', + options: { + context: { + _, + chalkRed: chalk.red, + fs, + SERVER_TEST_SRC_DIR, + }, + }, + renameTo: generator => `${generator.entityAbsoluteFolder}/web/rest/${generator.entityClass}ResourceIT.java`, + }, + ], + }, ], }; diff --git a/generators/server/templates/src/main/java/_package_/_entityPackage_/web/rest/_entityClass_Resource.java.ejs b/generators/server/templates/src/main/java/_package_/_entityPackage_/web/rest/_entityClass_Resource.java.ejs index 7de1afca7ced..9daec3e9eae5 100644 --- a/generators/server/templates/src/main/java/_package_/_entityPackage_/web/rest/_entityClass_Resource.java.ejs +++ b/generators/server/templates/src/main/java/_package_/_entityPackage_/web/rest/_entityClass_Resource.java.ejs @@ -125,6 +125,9 @@ import java.util.stream.Collectors; import java.util.stream.StreamSupport; <%_ } _%> <%_ } _%> +<%_ if (hasRolesSecurity) { _%> +import org.springframework.security.access.annotation.Secured; +<%_ } _%> /** * REST controller for managing {@link <%= entityAbsoluteClass %>}. @@ -161,9 +164,15 @@ public class <%= entityClass %>Resource { * * @param <%= instanceName %> the <%= instanceName %> to create. * @return the {@link ResponseEntity} with status {@code 201 (Created)} and with body the new <%= instanceName %>, or with status {@code 400 (Bad Request)} if the <%= entityInstance %> has already an ID. + <%_ if (hasRolesSecurity) { _%> + * If the user does not have authority to access the endpoint it will return status {@code 403 (Forbidden)} . + <%_ } _%> * @throws URISyntaxException if the Location URI syntax is incorrect. */ @PostMapping("/<%= entityApiUrl %>") + <%_ if (hasRolesSecurity && security.roles.post) { _%> + @Secured({<%- security.roles.post %>}) + <%_ }_%> public <% if (reactive) { %>Mono<<% } %>ResponseEntity<<%= instanceType %>><% if (reactive) { %>><% } %> create<%= entityClass %>(<% if (anyPropertyHasValidation) { %>@Valid <% } %>@RequestBody <%= instanceType %> <%= instanceName %>) throws URISyntaxException { log.debug("REST request to save <%= entityClass %> : {}", <%= instanceName %>); if (<%= instanceName %>.get<%= primaryKey.nameCapitalized %>() != null) { @@ -214,10 +223,16 @@ public class <%= entityClass %>Resource { * @param <%= instanceName %> the <%= instanceName %> to update. * @return the {@link ResponseEntity} with status {@code 200 (OK)} and with body the updated <%= instanceName %>, * or with status {@code 400 (Bad Request)} if the <%= instanceName %> is not valid, + <%_ if (hasRolesSecurity) { _%> + * or with status {@code 403 (Forbidden)} if the user does not have authority to access the endpoint, + <%_ } _%> * or with status {@code 500 (Internal Server Error)} if the <%= instanceName %> couldn't be updated. * @throws URISyntaxException if the Location URI syntax is incorrect. */ @PutMapping("/<%= entityApiUrl %>/{<%= primaryKey.name %>}") + <%_ if (hasRolesSecurity && security.roles.put) { _%> + @Secured({<%- security.roles.put %>}) + <%_ }_%> public <% if (reactive) { %>Mono<<% } %>ResponseEntity<<%= instanceType %>><% if (reactive) { %>><% } %> update<%= entityClass %>( @PathVariable(value = "<%= primaryKey.name %>", required = false) final <%= primaryKey.type %> <%= primaryKey.name %>, <% if (anyPropertyHasValidation) { %>@Valid <% } %>@RequestBody <%= instanceType %> <%= instanceName %> @@ -279,11 +294,17 @@ public class <%= entityClass %>Resource { * @param <%= instanceName %> the <%= instanceName %> to update. * @return the {@link ResponseEntity} with status {@code 200 (OK)} and with body the updated <%= instanceName %>, * or with status {@code 400 (Bad Request)} if the <%= instanceName %> is not valid, + <%_ if (hasRolesSecurity) { _%> + * or with status {@code 403 (Forbidden)} if the user does not have authority to access the endpoint, + <%_ } _%> * or with status {@code 404 (Not Found)} if the <%= instanceName %> is not found, * or with status {@code 500 (Internal Server Error)} if the <%= instanceName %> couldn't be updated. * @throws URISyntaxException if the Location URI syntax is incorrect. */ @PatchMapping(value = "/<%= entityApiUrl %>/{<%= primaryKey.name %>}", consumes = {"application/json", "application/merge-patch+json"}) + <%_ if (hasRolesSecurity && security.roles.put) { _%> + @Secured({<%- security.roles.put %>}) + <%_ }_%> public <% if (reactive) { %>Mono<<% } %>ResponseEntity<<%= instanceType %>><% if (reactive) { %>><% } %> partialUpdate<%= entityClass %>( @PathVariable(value = "<%= primaryKey.name %>", required = false) final <%= primaryKey.type %> <%= primaryKey.name %>, <% if (anyPropertyHasValidation) { %>@NotNull <% } %>@RequestBody <%= instanceType %> <%= instanceName %>) throws URISyntaxException { @@ -358,12 +379,18 @@ public class <%= entityClass %>Resource { * @param filter the filter of the request. <%_ } _%> * @return the {@link ResponseEntity} with status {@code 200 (OK)} and the list of <%= entityInstancePlural %> in body. +<%_ if (hasRolesSecurity) { _%> + * If the user does not have authority to access the endpoint it will return status {@code 403 (Forbidden)} . +<%_ } _%> */ <%_ if (reactive) { _%> @GetMapping(value = "/<%= entityApiUrl %>", produces = MediaType.APPLICATION_JSON_VALUE) <%_ } else { _%> @GetMapping("/<%= entityApiUrl %>") <%_ } _%> + <%_ if (hasRolesSecurity && security.roles.get) { _%> + @Secured({<%- security.roles.get %>}) + <%_ }_%> <%_ if (databaseTypeSql && isUsingMapsId && !viaService) { _%> @Transactional(readOnly = true) <%_ } _%> @@ -373,8 +400,14 @@ public class <%= entityClass %>Resource { /** * {@code GET /<%= entityApiUrl %>} : get all the <%= entityInstancePlural %> as a stream. * @return the {@link Flux} of <%= entityInstancePlural %>. + <%_ if (hasRolesSecurity) { _%> + * If the user does not have authority to access the endpoint it will return status {@code 403 (Forbidden)} . + <%_ } _%> */ @GetMapping(value = "/<%= entityApiUrl %>", produces = MediaType.APPLICATION_NDJSON_VALUE) + <%_ if (hasRolesSecurity && security.roles.get) { _%> + @Secured({<%- security.roles.get %>}) + <%_ }_%> <%_ if (databaseTypeSql && isUsingMapsId && !viaService) { _%> @Transactional(readOnly = true) <%_ } _%> @@ -393,8 +426,14 @@ public class <%= entityClass %>Resource { * * @param id the id of the <%= instanceName %> to retrieve. * @return the {@link ResponseEntity} with status {@code 200 (OK)} and with body the <%= instanceName %>, or with status {@code 404 (Not Found)}. + <%_ if (hasRolesSecurity) { _%> + * If the user does not have authority to access the endpoint it will return status {@code 403 (Forbidden)} . + <%_ } _%> */ @GetMapping("/<%= entityApiUrl %>/{id}") + <%_ if (hasRolesSecurity && security.roles.get) { _%> + @Secured({<%- security.roles.get %>}) + <%_ }_%> <%_ if (databaseTypeSql && isUsingMapsId && !viaService) { _%> @Transactional(readOnly = true) <%_ } _%> @@ -409,8 +448,14 @@ public class <%= entityClass %>Resource { * * @param id the id of the <%= instanceName %> to delete. * @return the {@link ResponseEntity} with status {@code 204 (NO_CONTENT)}. + <%_ if (hasRolesSecurity) { _%> + * If the user does not have authority to access the endpoint it will return status {@code 403 (Forbidden)} . + <%_ } _%> */ @DeleteMapping("/<%= entityApiUrl %>/{id}") + <%_ if (hasRolesSecurity && security.roles.delete) { _%> + @Secured({<%- security.roles.delete %>}) + <%_ }_%> public <% if (reactive) { %>Mono<<% } %>ResponseEntity<% if (reactive) { %>><% } %> delete<%= entityClass %>(@PathVariable <%= primaryKey.type %> id) { log.debug("REST request to delete <%= entityClass %> : {}", id); <%- include('../../_partials_entity_/delete_template', {viaService: viaService, fromResource: true}); -%> @@ -443,7 +488,15 @@ public class <%= entityClass %>Resource { <%_ } _%> <%_ } _%> * @return the result of the search. + <%_ if (hasRolesSecurity) { _%> + * If the user does not have authority to access the endpoint it will return status {@code 403 (Forbidden)} . + <%_ } _%> */ + <%_ if (hasRolesSecurity && security.roles.get) { _%> + @GetMapping("/_search/<%= entityApiUrl %>") + @Secured({<%- security.roles.get %>})<%- include('../../_partials_entity_/search_template', {viaService}); -%> + <%_ } else { _%> @GetMapping("/_search/<%= entityApiUrl %>")<%- include('../../_partials_entity_/search_template', {viaService}); -%> + <%_ } _%> <%_ } _%> } diff --git a/generators/server/templates/src/test/java/_package_/_entityPackage_/web/rest/_entityClass_secRoles_ResourceIT.java.ejs b/generators/server/templates/src/test/java/_package_/_entityPackage_/web/rest/_entityClass_secRoles_ResourceIT.java.ejs new file mode 100644 index 000000000000..4fa0496aa1d2 --- /dev/null +++ b/generators/server/templates/src/test/java/_package_/_entityPackage_/web/rest/_entityClass_secRoles_ResourceIT.java.ejs @@ -0,0 +1,2318 @@ +<%# + Copyright 2013-2023 the original author or authors from the JHipster project. + + This file is part of the JHipster project, see https://www.jhipster.tech/ + for more information. + + Licensed 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 + + https://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. +#%> +package <%= entityAbsolutePackage %>.web.rest; + +<%_ +var filterTestableRelationships = reactive ? reactiveEagerRelations : relationships; +const fieldsToTest = fields.filter(field => !field.id && !field.autoGenerate && !field.transient); +let mapsIdEntity; +let mapsIdEntityInstance; +let mapsIdRepoInstance; +if (isUsingMapsId) { + mapsIdEntity = mapsIdAssoc.otherEntityNameCapitalized; + mapsIdEntityInstance = mapsIdEntity.charAt(0).toLowerCase() + mapsIdEntity.slice(1); + mapsIdRepoInstance = `${mapsIdEntityInstance}Repository`; +} + +let callBlock = ''; +let callListBlock = ''; +if (reactive) { + callBlock = ".block()"; + callListBlock = ".collectList().block()"; +} +let saveMethod = 'save'; +if (!reactive && databaseTypeSql) { + saveMethod = 'saveAndFlush'; +} +let createEntityPrefix = ''; +let createEntityPostfix = ''; +if (databaseTypeSql && reactive) { + createEntityPrefix = 'em.insert('; + createEntityPostfix = ').block()'; +} +let idValue = `${persistInstance}.get${primaryKey.nameCapitalized}()`; +if (primaryKey.typeLong || primaryKey.typeInteger) { + idValue = idValue + '.intValue()'; +} else if (primaryKey.typeUUID) { + idValue = idValue + '.toString()'; +} +let transactionalAnnotation = ''; +if (databaseTypeSql && !reactive) { + transactionalAnnotation = '\n @Transactional'; +} + +_%> +<%_ +// prepare roles security variables +let getDefaultRole = "ROLE_NAME"; +let postDefaultRole = "ROLE_NAME"; +let putDefaultRole = "ROLE_NAME"; +let deleteDefaultRole = "ROLE_NAME"; +let missingActions = []; +let getTestPrefix = "// @Test"; +let postTestPrefix = "// @Test"; +let putTestPrefix = "// @Test"; +let deleteTestPrefix = "// @Test"; +let allowedGetRoles = [ ... security.allowedRoles.get ]; +let allowedPostRoles = [ ... security.allowedRoles.post ]; +let allowedPutRoles = [ ... security.allowedRoles.put ]; +let allowedDeleteRoles = [ ... security.allowedRoles.delete ]; +if (security.allowedRoles.get.length > 0) { + getTestPrefix = "@Test"; + getDefaultRole = security.allowedRoles.get[0]; +} else { + missingActions.push("GET"); + allowedGetRoles.push("ROLE_NAME"); +} +if (security.allowedRoles.post.length > 0) { + postTestPrefix = "@Test"; + postDefaultRole = security.allowedRoles.post[0]; +} else { + missingActions.push("POST"); + allowedPostRoles.push("ROLE_NAME"); +} +if (security.allowedRoles.put.length > 0) { + putTestPrefix = "@Test"; + putDefaultRole = security.allowedRoles.put[0]; +} else { + missingActions.push("PUT"); + allowedPutRoles.push("ROLE_NAME"); +} +if (security.allowedRoles.delete.length > 0) { + deleteTestPrefix = "@Test"; + deleteDefaultRole = security.allowedRoles.delete[0]; +} else { + missingActions.push("DELETE"); + allowedDeleteRoles.push("ROLE_NAME"); +} +let missingActionsWarning = undefined; +if (missingActions.length > 0) { + missingActionsWarning = "// TODO: define a role(s) with the permission to do " + missingActions.join(', ') + entityInstance + " and uncomment the test(s) below" +} +_%> +<%_ if (missingActionsWarning) { _%> +// +<%= missingActionsWarning %> +// + +<%_ } _%> +<%_ if (entityAbsolutePackage !== packageName) { _%> +import <%= packageName %>.web.rest.TestUtil; +<%_ } _%> +import <%= packageName %>.IntegrationTest; +import <%= entityAbsolutePackage %>.domain.<%= persistClass %>; +<%_ +var imported = []; +for (relationship of relationships) { // import entities in required relationships + const relationshipValidate = relationship.relationshipValidate; + const otherEntityNameCapitalized = relationship.otherEntityNameCapitalized; + const isUsingMapsIdL1 = relationship.id; + if (imported.indexOf(otherEntityNameCapitalized) === -1) { + if ((relationshipValidate !== null && relationshipValidate === true) || jpaMetamodelFiltering || (isUsingMapsIdL1 === true)) { _%> +import <%= entityAbsolutePackage %>.domain.<%= relationship.otherEntity.persistClass %>; +<%_ + imported.push(otherEntityNameCapitalized); + } + } +} _%> +<%_ +if(jpaMetamodelFiltering && reactive) { + filterTestableRelationships.forEach(relationship => { _%> +import <%= entityAbsolutePackage %>.repository.<%= relationship.otherEntityNameCapitalized %>Repository; +<%_ }); + } _%> +<%_ if (saveUserSnapshot) { _%> +import <%= entityAbsolutePackage %>.repository.UserRepository; +<%_ } _%> +import <%= entityAbsolutePackage %>.repository.<%= entityClass %>Repository; +<%_ if (databaseTypeSql && reactive) { _%> +import <%= packageName %>.repository.EntityManager; +<%_ } _%> +<%_ if (isUsingMapsId && (!dtoMapstruct && serviceNo)) { _%> +import <%= entityAbsolutePackage %>.repository.<%= mapsIdAssoc.otherEntityNameCapitalized %>Repository; +<%_ } _%> +<%_ if (searchEngineElasticsearch) { _%> +import <%= entityAbsolutePackage %>.repository.search.<%= entityClass %>SearchRepository; +<%_ } _%> +<%_ if (!serviceNo && implementsEagerLoadApis) { _%> +import <%= entityAbsolutePackage %>.service.<%= entityClass %>Service; +<%_ } _%> +<%_ if (dtoMapstruct) { _%> +import <%= entityAbsolutePackage %>.service.dto.<%= dtoClass %>; +import <%= entityAbsolutePackage %>.service.mapper.<%= entityClass %>Mapper; +<%_ } _%> +<%_ if (jpaMetamodelFiltering && !reactive) { _%> +import <%= entityAbsolutePackage %>.service.criteria.<%= entityClass %>Criteria; +<%_ } _%> +<%_ if (searchEngineElasticsearch) { _%> +import org.assertj.core.util.IterableUtil; +import org.apache.commons.collections4.IterableUtils; +import java.util.concurrent.TimeUnit; +<%_ } _%> +<%_ if ((databaseTypeSql && reactive) || searchEngineElasticsearch) { _%> +import org.junit.jupiter.api.AfterEach; +<%_ } _%> +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +<%_ if (implementsEagerLoadApis || databaseTypeNeo4j) { _%> +import org.mockito.Mock; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.junit.jupiter.MockitoExtension; +<%_ } _%> +import org.springframework.beans.factory.annotation.Autowired; +<%_ if (reactive) { _%> +import org.springframework.boot.test.autoconfigure.web.reactive.AutoConfigureWebTestClient; +<%_ } else { _%> +import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; +<%_ } _%> +<%_ if (searchEngineElasticsearch && !paginationNo || implementsEagerLoadApis) { _%> +import org.springframework.data.domain.PageImpl; +import org.springframework.data.domain.Pageable; +import org.springframework.data.domain.PageRequest; +<%_ } _%> +import org.springframework.http.MediaType; +<%_ if (searchEngineCouchbase) { _%> +import java.util.concurrent.TimeUnit; + +import org.junit.jupiter.api.Timeout; +<%_ } _%> +<%_ if (databaseTypeCouchbase) { _%> +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.security.test.context.TestSecurityContextHolder; +<%_ } _%> +import org.springframework.security.test.context.support.WithMockUser; +<%_ if (reactive) { _%> +import org.springframework.test.web.reactive.server.WebTestClient; +<%_ } _%> +<%_ if (!reactive) { _%> +import org.springframework.test.web.servlet.MockMvc; + <%_ if (databaseTypeSql) { _%> +import org.springframework.transaction.annotation.Transactional; + <%_ } _%> +<%_ } _%> +<%_ if (anyFieldIsBlobDerived) { _%> +import org.springframework.util.Base64Utils; +<%_ } _%> +<%_ if (reactive && (implementsEagerLoadApis || searchEngineElasticsearch)) { _%> +import reactor.core.publisher.Flux; +import reactor.core.publisher.Mono; +<%_ } _%> +<%_ if (databaseTypeSql && !reactive) { _%> +import jakarta.persistence.EntityManager; +<%_ } _%> +<%_ if (anyFieldIsBigDecimal) { _%> +import java.math.BigDecimal; +<%_ } _%> +<%_ if (anyFieldIsBlobDerived && databaseTypeCassandra) { _%> +import java.nio.ByteBuffer; +<%_ } _%> +<%_ if (reactive || anyFieldIsDuration) { _%> +import java.time.Duration; +<%_ } _%> +<%_ if (anyFieldIsLocalDate) { _%> +import java.time.LocalDate; +<%_ } _%> +<%_ if (anyFieldIsInstant || anyFieldIsZonedDateTime) { _%> +import java.time.Instant; +<%_ } _%> +<%_ if (anyFieldIsZonedDateTime) { _%> +import java.time.ZonedDateTime; +import java.time.ZoneOffset; +<%_ } _%> +<%_ if (anyFieldIsLocalDate || anyFieldIsZonedDateTime) { _%> +import java.time.ZoneId; +<%_ } _%> +<%_ if (anyFieldIsInstant) { _%> +import java.time.temporal.ChronoUnit; +<%_ } _%> +<%_ if (!reactive && implementsEagerLoadApis) { _%> +import java.util.ArrayList; +<%_ } _%> +<%_ if (searchEngineElasticsearch && !reactive) { _%> +import java.util.Collections; + <%_ if (paginationNo) { _%> +import java.util.stream.Stream; + <%_ } _%> +<%_ } _%> +import java.util.List; +<%_ if (anyFieldIsUUID || primaryKey.typeString || otherEntityPrimaryKeyTypesIncludesUUID) { _%> +import java.util.UUID; +<%_ } _%> +<%_ if (!embedded && (primaryKey.hasLong || primaryKey.hasInteger)) { _%> +import java.util.Random; + <%_ if (primaryKey.hasLong) { _%> +import java.util.concurrent.atomic.AtomicLong; + <%_ } else if (primaryKey.hasInteger) { _%> +import java.util.concurrent.atomic.AtomicInteger; + <%_ } _%> +<%_ } _%> + +<%_ if (anyFieldIsBigDecimal) { _%> +import static <%= packageName %>.web.rest.TestUtil.sameNumber; +<%_ } _%> +<%_ if (anyFieldIsZonedDateTime) { _%> +import static <%= packageName %>.web.rest.TestUtil.sameInstant; +<%_ } _%> +import static org.assertj.core.api.Assertions.assertThat; +import static org.hamcrest.Matchers.hasItem; +<%_ if (reactive) { _%> +import static org.hamcrest.Matchers.is; +<%_ } _%> +<%_ if (authenticationUsesCsrf) { _%> + <%_ if (reactive) { _%> +import static org.springframework.security.test.web.reactive.server.SecurityMockServerConfigurers.csrf; + <%_ } else { _%> +import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.csrf; + <%_ } _%> +<%_ } _%> +<%_ if (searchEngineElasticsearch || implementsEagerLoadApis) { _%> +import static org.mockito.Mockito.*; +<%_ } _%> +<%_ if (!reactive) { _%> +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*; +<%_ } _%> +<%_ if (searchEngineElasticsearch) { _%> +import static org.awaitility.Awaitility.await; +<%_ } _%> +<%_ for (const field of fields.filter(field => !field.transient)) { + if (field.fieldIsEnum) { _%> +import <%= entityAbsolutePackage %>.domain.enumeration.<%= field.fieldType %>; +<%_ } +} _%> +/** + * Integration tests for the {@link <%= entityClass %>Resource} REST controller. + */ +@IntegrationTest +<%_ if (implementsEagerLoadApis) { _%> +@ExtendWith(MockitoExtension.class) +<%_ } _%> +<%_ if (reactive) { _%> +@AutoConfigureWebTestClient(timeout = IntegrationTest.DEFAULT_ENTITY_TIMEOUT) +<%_ } else { _%> +@AutoConfigureMockMvc +<%_ } _%> +@WithMockUser +class <%= entityClass %>ResourceIT { +<%_ +for (field of fieldsToTest) { +const defaultValueName = 'DEFAULT_' + field.fieldNameUnderscored.toUpperCase(); +const updatedValueName = 'UPDATED_' + field.fieldNameUnderscored.toUpperCase(); +const smallerValueName = 'SMALLER_' + field.fieldNameUnderscored.toUpperCase(); +const needsSmallerValueName = jpaMetamodelFiltering && field.filterableField + && (field.fieldTypeNumeric || field.fieldTypeDuration || field.fieldTypeLocalDate || field.fieldTypeZonedDateTime); + +let defaultValue = 1; +let updatedValue = 2; + +if (field.fieldValidate === true) { + if (field.fieldValidationMax) { + defaultValue = field.fieldValidateRulesMax; + updatedValue = parseInt(field.fieldValidateRulesMax) - 1; + } + if (field.fieldValidationMin) { + defaultValue = field.fieldValidateRulesMin; + updatedValue = parseInt(field.fieldValidateRulesMin) + 1; + } + if (field.fieldValidationMinBytes) { + defaultValue = field.fieldValidateRulesMinbytes; + updatedValue = field.fieldValidateRulesMinbytes; + } + if (field.fieldValidationMaxBytes) { + updatedValue = field.fieldValidateRulesMaxbytes; + } +} + +const fieldType = field.fieldType; +const isEnum = field.fieldIsEnum; +let enumValue1; +let enumValue2; +if (isEnum) { + const enumValues = field.enumValues; + enumValue1 = enumValues[0]; + if (enumValues.length > 1) { + enumValue2 = enumValues[1]; + } else { + enumValue2 = enumValue1; + } +} + +if (field.fieldTypeString || field.blobContentTypeText) { + // Generate Strings, using the min and max string length if they are configured + let sampleTextString = ""; + let updatedTextString = ""; + let sampleTextLength = 10; + if (field.fieldValidateRulesMinlength > sampleTextLength) { + sampleTextLength = field.fieldValidateRulesMinlength; + } + if (field.fieldValidateRulesMaxlength < sampleTextLength) { + sampleTextLength = field.fieldValidateRulesMaxlength; + } + for (let i = 0; i < sampleTextLength; i++) { + sampleTextString += "A"; + updatedTextString += "B"; + } + if (field.fieldValidateRulesPattern !== undefined) { + // Generate Strings, using pattern + try { + const patternRegExp = new RegExp(field.fieldValidateRulesPattern); + const randExp = field.createRandexp(); + // set infinite repetitions max range + if (!patternRegExp.test(sampleTextString.replace(/\\"/g, '"').replace(/\\\\/g, '\\'))) { + sampleTextString = randExp.gen().replace(/\\/g, '\\\\').replace(/"/g, '\\"'); + } + if (!patternRegExp.test(updatedTextString.replace(/\\"/g, '"').replace(/\\\\/g, '\\'))) { + updatedTextString = randExp.gen().replace(/\\/g, '\\\\').replace(/"/g, '\\"'); + } + } catch (error) { + log(this.chalkRed('Error generating test value for entity "' + entityClass + + '" field "' + field.fieldName + '" with pattern "' + field.fieldValidateRulesPattern + + '", generating default values for this field. Detailed error message: "' + error.message + '".')); + } + if (sampleTextString === updatedTextString) { + updatedTextString = updatedTextString + "B"; + log(this.chalkRed('Randomly generated first and second test values for entity "' + entityClass + + '" field "' + field.fieldName + '" with pattern "' + field.fieldValidateRulesPattern + + '" in file "' + entityClass + 'ResourceIT" where equal, added symbol "B" to second value.')); + } + } _%> + + private static final String <%= defaultValueName %> = "<%- sampleTextString %>"; + private static final String <%= updatedValueName %> = "<%- updatedTextString %>"; + <%_ } else if (field.fieldTypeInteger) { _%> + + private static final Integer <%= defaultValueName %> = <%= defaultValue %>; + private static final Integer <%= updatedValueName %> = <%= updatedValue %>; + <%_ if (needsSmallerValueName) { _%> + private static final Integer <%= smallerValueName %> = <%= defaultValue %> - 1; + <%_ } _%> + <%_ } else if (field.fieldTypeLong) { _%> + + private static final Long <%= defaultValueName %> = <%= defaultValue %>L; + private static final Long <%= updatedValueName %> = <%= updatedValue %>L; + <%_ if (needsSmallerValueName) { _%> + private static final Long <%= smallerValueName %> = <%= defaultValue %>L - 1L; + <%_ } _%> + <%_ } else if (field.fieldTypeFloat) { _%> + + private static final <%= fieldType %> <%= defaultValueName %> = <%= defaultValue %>F; + private static final <%= fieldType %> <%= updatedValueName %> = <%= updatedValue %>F; + <%_ if (needsSmallerValueName) { _%> + private static final <%= fieldType %> <%= smallerValueName %> = <%= defaultValue %>F - 1F; + <%_ } _%> + <%_ } else if (field.fieldTypeDouble) { _%> + + private static final <%= fieldType %> <%= defaultValueName %> = <%= defaultValue %>D; + private static final <%= fieldType %> <%= updatedValueName %> = <%= updatedValue %>D; + <%_ if (needsSmallerValueName) { _%> + private static final <%= fieldType %> <%= smallerValueName %> = <%= defaultValue %>D - 1D; + <%_ } _%> + <%_ } else if (field.fieldTypeBigDecimal) { _%> + + private static final BigDecimal <%= defaultValueName %> = new BigDecimal(<%= defaultValue %>); + private static final BigDecimal <%= updatedValueName %> = new BigDecimal(<%= updatedValue %>); + <%_ if (needsSmallerValueName) { _%> + private static final BigDecimal <%= smallerValueName %> = new BigDecimal(<%= defaultValue %> - 1); + <%_ } _%> + <%_ } else if (field.fieldTypeUUID) { _%> + + private static final UUID <%= defaultValueName %> = UUID.randomUUID(); + private static final UUID <%= updatedValueName %> = UUID.randomUUID(); + <%_ } else if (field.fieldTypeLocalDate) { _%> + + private static final LocalDate <%= defaultValueName %> = LocalDate.ofEpochDay(0L); + private static final LocalDate <%= updatedValueName %> = LocalDate.now(ZoneId.systemDefault()); + <%_ if (needsSmallerValueName) { _%> + private static final LocalDate <%= smallerValueName %> = LocalDate.ofEpochDay(-1L); + <%_ } _%> + <%_ } else if (field.fieldTypeInstant) { _%> + + private static final Instant <%= defaultValueName %> = Instant.ofEpochMilli(0L); + private static final Instant <%= updatedValueName %> = Instant.now().truncatedTo(ChronoUnit.MILLIS); + <%_ if (needsSmallerValueName) { _%> + private static final Instant <%= smallerValueName %> = Instant.ofEpochMilli(-1L); + <%_ } _%> + <%_ } else if (field.fieldTypeZonedDateTime) { _%> + + private static final ZonedDateTime <%= defaultValueName %> = ZonedDateTime.ofInstant(Instant.ofEpochMilli(0L), ZoneOffset.UTC); + private static final ZonedDateTime <%= updatedValueName %> = ZonedDateTime.now(ZoneId.systemDefault()).withNano(0); + <%_ if (needsSmallerValueName) { _%> + private static final ZonedDateTime <%= smallerValueName %> = ZonedDateTime.ofInstant(Instant.ofEpochMilli(-1L), ZoneOffset.UTC); + <%_ } _%> + <%_ } else if (field.fieldTypeDuration) { _%> + + private static final Duration <%= defaultValueName %> = Duration.ofHours(6); + private static final Duration <%= updatedValueName %> = Duration.ofHours(12); + <%_ if (needsSmallerValueName) { _%> + private static final Duration <%= smallerValueName %> = Duration.ofHours(5); + <%_ } _%> + <%_ } else if (field.fieldTypeBoolean) { _%> + + private static final Boolean <%= defaultValueName %> = false; + private static final Boolean <%= updatedValueName %> = true; + <%_ } else if (field.fieldTypeBinary && !field.blobContentTypeText) { _%> + + <%_ if (!databaseTypeCassandra) { _%> + private static final byte[] <%= defaultValueName %> = TestUtil.createByteArray(1, "0"); + private static final byte[] <%= updatedValueName %> = TestUtil.createByteArray(1, "1"); + <%_ } else { _%> + private static final ByteBuffer <%= defaultValueName %> = ByteBuffer.wrap(TestUtil.createByteArray(1, "0")); + private static final ByteBuffer <%= updatedValueName %> = ByteBuffer.wrap(TestUtil.createByteArray(1, "1")); + <%_ } _%> + private static final String <%= defaultValueName %>_CONTENT_TYPE = "image/jpg"; + private static final String <%= updatedValueName %>_CONTENT_TYPE = "image/png"; + <%_ } else if (isEnum) { _%> + + private static final <%= fieldType %> <%= defaultValueName %> = <%= fieldType %>.<%= enumValue1.name %>; + private static final <%= fieldType %> <%= updatedValueName %> = <%= fieldType %>.<%= enumValue2.name %>; + <%_ } +} _%> + + private static final String ENTITY_API_URL = "/api/<%= entityApiUrl %>"; + private static final String ENTITY_API_URL_ID = ENTITY_API_URL + "/{<%= primaryKey.name %>}"; +<%_ if (searchEngineAny) { _%> + private static final String ENTITY_SEARCH_API_URL = "/api/_search/<%= entityApiUrl %>"; +<%_ } _%> +<%_ if (!embedded && (primaryKey.hasLong || primaryKey.hasInteger)) { _%> + + private static Random random = new Random(); + <%_ if (primaryKey.hasLong) { _%> + private static AtomicLong count = new AtomicLong(random.nextInt() + ( 2 * Integer.MAX_VALUE )); + <%_ } else if (primaryKey.hasInteger) { _%> + private static AtomicInteger count = new AtomicInteger(random.nextInt() + ( 2 * Short.MAX_VALUE )); + <%_ } _%> +<%_ } _%> + + @Autowired + private <%= entityClass %>Repository <%= entityInstance %>Repository; +<%_ if (isUsingMapsId && (!dtoMapstruct && serviceNo)) { _%> + @Autowired + private <%= mapsIdEntity %>Repository <%= mapsIdRepoInstance %>; +<%_ } _%> +<%_ if (saveUserSnapshot) { _%> + + @Autowired + private UserRepository userRepository; +<%_ } _%> +<%_ if (implementsEagerLoadApis) { _%> + + @Mock + private <%= entityClass %>Repository <%= entityInstance %>RepositoryMock; +<%_ } _%> +<%_ if (dtoMapstruct) { _%> + + @Autowired + private <%= entityClass %>Mapper <%= entityInstance %>Mapper; +<%_ } if (!serviceNo) { _%> + <%_ if (implementsEagerLoadApis) { _%> + + @Mock + private <%= entityClass %>Service <%= entityInstance %>ServiceMock; + <%_ } _%> +<%_ } if (searchEngineElasticsearch) { _%> + + @Autowired + private <%= entityClass %>SearchRepository <%= entityInstance %>SearchRepository; +<%_ } _%> +<%_ if (databaseTypeSql) { _%> + + @Autowired + private EntityManager em; +<%_ } _%> + + @Autowired +<%_ if (reactive) { _%> + private WebTestClient webTestClient; +<%_ } else { _%> + private MockMvc rest<%= entityClass %>MockMvc; +<%_ } _%> + + private <%= persistClass %> <%= persistInstance %>; + +<%_ if(jpaMetamodelFiltering && reactive) { +filterTestableRelationships.forEach((relationship) => { _%> + @Autowired + private <%= relationship.otherEntity.persistClass %>Repository <%= relationship.otherEntity.persistInstance %>Repository; +<%_ }); +}_%> +<%_ ['DEFAULT_', 'UPDATED_'].forEach((fieldStatus) => { _%> + /** + * Create an <% if (fieldStatus === 'UPDATED_') { %>updated <% } %>entity for this test. + * + * This is a static method, as tests for other entities might also need it, + * if they test an entity which requires the current entity. + */ + public static <%= persistClass %> create<% if (fieldStatus === 'UPDATED_') { _%>Updated<%_ } %>Entity(<% if (databaseTypeSql) { %>EntityManager em<% } %>) { + <%_ if (fluentMethods) { _%> + <%= persistClass %> <%= persistInstance %> = new <%= persistClass %>()<%_ if (reactive && databaseTypeSql && primaryKey.typeUUID && !isUsingMapsId) { _%> + .<%= primaryKey.name %>(UUID.randomUUID()) + <%_ } _%><% for (field of fieldsToTest) { %> + .<%= field.fieldName %>(<%= fieldStatus + field.fieldNameUnderscored.toUpperCase() %>)<% if (field.fieldTypeBinary && !field.blobContentTypeText) { %> + .<%= field.fieldName %>ContentType(<%= fieldStatus + field.fieldNameUnderscored.toUpperCase() %>_CONTENT_TYPE)<% } %><% } %>; + <%_ } else { _%> + <%= persistClass %> <%= persistInstance %> = new <%= persistClass %>(); + <%_ if (reactive && databaseTypeSql && primaryKey.typeUUID && !isUsingMapsId) { _%> + <%= persistInstance %>.set<%= primaryKey.fields[0].fieldInJavaBeanMethod %>(UUID.randomUUID()); + <%_ } _%> + <%_ for (field of fieldsToTest) { _%> + <%= persistInstance %>.set<%= field.fieldInJavaBeanMethod %>(<%= fieldStatus + field.fieldNameUnderscored.toUpperCase() %>); + <%_ if (field.fieldTypeBinary && !field.blobContentTypeText) { _%> + <%= persistInstance %>.set<%= field.fieldInJavaBeanMethod %>ContentType(<%= fieldStatus + field.fieldNameUnderscored.toUpperCase() %>_CONTENT_TYPE); + <%_ } _%> + <%_ } _%> + <%_ } _%> + <%_ + const alreadyGeneratedEntities = []; + for (relationship of relationships) { + const relationshipValidate = relationship.relationshipValidate; + const otherEntityName = relationship.otherEntityName; + const otherEntityNameCapitalized = relationship.otherEntityNameCapitalized; + const relationshipNameCapitalizedPlural = relationship.relationshipNameCapitalizedPlural; + const relationshipNameCapitalized = relationship.relationshipNameCapitalized; + const mapsIdUse = relationship.id; + if ((relationshipValidate !== null && relationshipValidate) || mapsIdUse) { _%> + // Add required entity + <%_ if (alreadyGeneratedEntities.indexOf(otherEntityName) == -1) { _%> + <%_ if (relationship.otherEntityUser) { /* TODO or other entity has no unique fields */ _%> + <%= relationship.otherEntity.persistClass %> <%= otherEntityName %> = <%= createEntityPrefix %><%= otherEntityNameCapitalized %>ResourceIT.createEntity(<% if (databaseTypeSql) { %>em<% } %>)<%= createEntityPostfix %>; + <%_ if (databaseTypeSql && !reactive) { _%> + em.persist(<%= otherEntityName %>); + em.flush(); + <%_ } _%> + <%_ if (databaseTypeMongodb) { _%> + <%= otherEntityName %>.set<%= primaryKey.nameCapitalized %>("fixed-id-for-tests"); + <%_ } _%> + <%_ } else { _%> + <%= relationship.otherEntity.persistClass %> <%= otherEntityName %>; + <%_ if (databaseTypeSql && !reactive) { _%> + <%_ if (!isUsingMapsId || fieldStatus !== "UPDATED_") { _%> + if (TestUtil.findAll(em, <%= relationship.otherEntity.persistClass %>.class).isEmpty()) { + <%_ } _%> + <%= otherEntityName %> = <%= createEntityPrefix %><%= otherEntityNameCapitalized %>ResourceIT.create<% if (fieldStatus === 'UPDATED_') { %>Updated<% } %>Entity(em)<%= createEntityPostfix %>; + em.persist(<%= otherEntityName %>); + em.flush(); + <%_ if (!isUsingMapsId || fieldStatus !== "UPDATED_") { _%> + } else { + <%= otherEntityName %> = TestUtil.findAll(em, <%= relationship.otherEntity.persistClass %>.class).get(0); + } + <%_ } _%> + <%_ } else { _%> + <%= otherEntityName %> = <%= createEntityPrefix %><%= otherEntityNameCapitalized %>ResourceIT.create<% if (fieldStatus === 'UPDATED_') { %>Updated<% } %>Entity(<% if (databaseType === 'sql') { %>em<% } %>)<%= createEntityPostfix %>; + <%_ } _%> + <%_ if (databaseTypeMongodb) { _%> + <%= otherEntityName %>.set<%= primaryKey.nameCapitalized %>("fixed-id-for-tests"); + <%_ } _%> + <%_ } _%> + <%_ } _%> + <%_ if (relationship.relationshipManyToMany || relationship.relationshipOneToMany) { _%> + <%= persistInstance %>.get<%= relationshipNameCapitalizedPlural %>().add(<%= otherEntityName %>); + <%_ } else { _%> + <%= persistInstance %>.set<%= relationshipNameCapitalized %>(<%= otherEntityName %>); + <%_ } _%> + <%_ alreadyGeneratedEntities.push(otherEntityName) _%> + <%_ } _%> + <%_ } _%> + return <%= persistInstance %>; + } +<%_ }); _%> + +<%_ if (databaseTypeSql && reactive) { + const alreadyGeneratedDeletionCalls = []; +_%> + public static void deleteEntities(EntityManager em) { + try { + <%_ relationships.forEach(function(rel) { + if (rel.shouldWriteJoinTable) { _%> + em.deleteAll("<%= rel.joinTable.name %>").block(); + <%_ } _%> + <%_ }); _%> + em.deleteAll(<%= persistClass %>.class).block(); + } catch (Exception e) { + // It can fail, if other entities are still referring this - it will be removed later. + } + <%_ relationships.forEach(function(rel) { + if ((rel.relationshipValidate || rel.id) && !alreadyGeneratedDeletionCalls.includes(rel.otherEntityName)) { _%> + <%= rel.otherEntityNameCapitalized %>ResourceIT.deleteEntities(em); + <%_ alreadyGeneratedDeletionCalls.push(rel.otherEntityName); + } + }); _%> + } + + @AfterEach + public void cleanup() { + deleteEntities(em); + } + +<%_ } _%> +<%_ if (searchEngineElasticsearch) { _%> + @AfterEach + public void cleanupElasticSearchRepository() { + <%= entityInstance %>SearchRepository.deleteAll()<%= callBlock %>; + assertThat(<%= entityInstance %>SearchRepository.count()<%= callBlock %>).isEqualTo(0); + } + +<%_ } _%> +<%_ if (reactive && authenticationUsesCsrf) { _%> + @BeforeEach + public void setupCsrf() { + webTestClient = webTestClient.mutateWith(csrf()); + } + +<%_ } _%> + @BeforeEach + public void initTest() { +<%_ if (databaseTypeMongodb || databaseTypeCouchbase || databaseTypeCassandra || databaseTypeNeo4j) { _%> + <%= entityInstance %>Repository.deleteAll()<%= callBlock %>; +<%_ } else if (databaseTypeSql && reactive) { _%> + deleteEntities(em); +<%_ } _%> + <%= persistInstance %> = createEntity(<% if (databaseTypeSql) { %>em<% } %>); + } +<%_ if (!readOnly) { _%> + + void create<%= entityClass %>IsAllowed() throws Exception { + int databaseSizeBeforeCreate = <%= entityInstance %>Repository.findAll()<%= callListBlock %>.size(); + <%_ if (searchEngineElasticsearch) { _%> + int searchDatabaseSizeBefore = IterableUtil.sizeOf(<%= entityInstance %>SearchRepository.findAll()<%= callListBlock %>); + <%_ } _%> + <%_ + // overwrite the id field again with null + // the create method here is supposed to be used for other tests as well, + // which may expect an id to be set (at least in the reactive stack) + if (reactive && databaseTypeSql && primaryKey.typeUUID && !isUsingMapsId) { _%> + <%= persistInstance %>.set<%= primaryKey.nameCapitalized %>(null); + <%_ } _%> + // Create the <%= entityClass %> + <%_ if (dtoMapstruct) { _%> + <%= dtoClass %> <%= dtoInstance %> = <%= entityInstance %>Mapper.toDto(<%= persistInstance %>); + <%_ } _%> + <%_ if (reactive) { _%> + webTestClient.post().uri(ENTITY_API_URL) + .contentType(MediaType.APPLICATION_JSON) + .bodyValue(TestUtil.convertObjectToJsonBytes(<%= restInstance %>)) + .exchange() + .expectStatus().isCreated(); + <%_ } else { _%> + rest<%= entityClass %>MockMvc.perform(post(ENTITY_API_URL)<% if (authenticationUsesCsrf) { %>.with(csrf())<% }%> + .contentType(MediaType.APPLICATION_JSON) + .content(TestUtil.convertObjectToJsonBytes(<%= restInstance %>))) + .andExpect(status().isCreated()); + <%_ } _%> + + // Validate the <%= entityClass %> in the database + <%_ if (databaseTypeCouchbase) { _%> + SecurityContextHolder.setContext(TestSecurityContextHolder.getContext()); + <%_ } _%> + List<<%= persistClass %>> <%= entityInstance %>List = <%= entityInstance %>Repository.findAll()<%= callListBlock %>; + assertThat(<%= entityInstance %>List).hasSize(databaseSizeBeforeCreate + 1); + <%_ if (searchEngineElasticsearch) { _%> + await().atMost(5, TimeUnit.SECONDS).untilAsserted(() -> { + int searchDatabaseSizeAfter = IterableUtil.sizeOf(<%= entityInstance %>SearchRepository.findAll()<%= callListBlock %>); + assertThat(searchDatabaseSizeAfter).isEqualTo(searchDatabaseSizeBefore + 1); + }); + <%_ } _%> + <%= persistClass %> test<%= entityClass %> = <%= entityInstance %>List.get(<%= entityInstance %>List.size() - 1); + <%_ for (const field of fieldsToTest) { + if (field.fieldTypeZonedDateTime) { _%> + assertThat(test<%= entityClass %>.get<%= field.fieldInJavaBeanMethod %>()).isEqualTo(<%= 'DEFAULT_' + field.fieldNameUnderscored.toUpperCase() %>); + <%_ } else if (field.fieldTypeBinary && !field.blobContentTypeText) { _%> + assertThat(test<%= entityClass %>.get<%= field.fieldInJavaBeanMethod %>()).isEqualTo(<%= 'DEFAULT_' + field.fieldNameUnderscored.toUpperCase() %>); + assertThat(test<%= entityClass %>.get<%= field.fieldInJavaBeanMethod %>ContentType()).isEqualTo(<%= 'DEFAULT_' + field.fieldNameUnderscored.toUpperCase() %>_CONTENT_TYPE); + <%_ } else if (field.fieldTypeBigDecimal) { _%> + assertThat(test<%= entityClass %>.get<%= field.fieldInJavaBeanMethod %>()).isEqualByComparingTo(<%= 'DEFAULT_' + field.fieldNameUnderscored.toUpperCase() %>); + <%_ } else { _%> + assertThat(test<%= entityClass %>.get<%= field.fieldInJavaBeanMethod %>()).isEqualTo(<%= 'DEFAULT_' + field.fieldNameUnderscored.toUpperCase() %>); + <%_ } + } _%> + <%_ if (isUsingMapsId) { _%> + + // Validate the id for MapsId, the ids must be same + assertThat(test<%= entityClass %>.get<%= primaryKey.nameCapitalized %>()).isEqualTo(<%_ if (dtoMapstruct) { _%><%= dtoInstance %><%_ } else { _%>test<%= entityClass %><%_ } _%>.get<%= mapsIdEntity %>().get<%= primaryKey.nameCapitalized %>()); + <%_ } _%> + } + <%_ allowedPostRoles.forEach(secRole => { _%> + + <%= postTestPrefix %><%= transactionalAnnotation %> + @WithMockUser(username = "user", authorities = {"<%= secRole %>"}) + void create<%= entityClass %>_with_<%= secRole %>() throws Exception { + create<%= entityClass %>IsAllowed(); + } + <%_ }); _%> + + void create<%= entityClass %>IsForbidden() throws Exception { + int databaseSizeBeforeCreate = <%= entityInstance %>Repository.findAll()<%= callListBlock %>.size(); + <%_ if (searchEngineElasticsearch) { _%> + int searchDatabaseSizeBefore = IterableUtil.sizeOf(<%= entityInstance %>SearchRepository.findAll()<%= callListBlock %>); + <%_ } _%> + <%_ + // overwrite the id field again with null + // the create method here is supposed to be used for other tests as well, + // which may expect an id to be set (at least in the reactive stack) + if (reactive && databaseTypeSql && primaryKey.typeUUID && !isUsingMapsId) { _%> + <%= persistInstance %>.set<%= primaryKey.nameCapitalized %>(null); + <%_ } _%> + // Create the <%= entityClass %> + <%_ if (dtoMapstruct) { _%> + <%= dtoClass %> <%= dtoInstance %> = <%= entityInstance %>Mapper.toDto(<%= persistInstance %>); + <%_ } _%> + <%_ if (reactive) { _%> + webTestClient.post().uri(ENTITY_API_URL) + .contentType(MediaType.APPLICATION_JSON) + .bodyValue(TestUtil.convertObjectToJsonBytes(<%= restInstance %>)) + .exchange() + .expectStatus().isForbidden(); + <%_ } else { _%> + rest<%= entityClass %>MockMvc.perform(post(ENTITY_API_URL)<% if (authenticationUsesCsrf) { %>.with(csrf())<% }%> + .contentType(MediaType.APPLICATION_JSON) + .content(TestUtil.convertObjectToJsonBytes(<%= restInstance %>))) + .andExpect(status().isForbidden()); + <%_ } _%> + + // Validate the <%= entityClass %> in the database + <%_ if (databaseTypeCouchbase) { _%> + SecurityContextHolder.setContext(TestSecurityContextHolder.getContext()); + <%_ } _%> + List<<%= persistClass %>> <%= entityInstance %>List = <%= entityInstance %>Repository.findAll()<%= callListBlock %>; + assertThat(<%= entityInstance %>List).hasSize(databaseSizeBeforeCreate); + <%_ if (searchEngineElasticsearch) { _%> + await().atMost(5, TimeUnit.SECONDS).untilAsserted(() -> { + int searchDatabaseSizeAfter = IterableUtil.sizeOf(<%= entityInstance %>SearchRepository.findAll()<%= callListBlock %>); + assertThat(searchDatabaseSizeAfter).isEqualTo(searchDatabaseSizeBefore); + }); + <%_ } _%> + } + <%_ security.forbiddenRoles.post.forEach(secRole => { _%> + + @Test<%= transactionalAnnotation %> + @WithMockUser(username = "user", authorities = {"<%= secRole %>"}) + void create<%= entityClass %>_with_<%= secRole %>() throws Exception { + create<%= entityClass %>IsForbidden(); + } + <%_ }); _%> + + void create<%= entityClass %>WithExistingId() throws Exception { + // Create the <%= entityClass %> with an existing ID + <%_ if (primaryKey.typeUUID && databaseTypeSql) { _%> + <%= entityInstance %>Repository.<%= saveMethod %>(<%= persistInstance %>)<%= callBlock %>; + <%_ } else { _%> + <%= persistInstance %>.set<%= primaryKey.nameCapitalized %>(<% if (primaryKey.typeUUID) { %>UUID.randomUUID()<% } else if (primaryKey.typeLong) { %>1L<% } else if (primaryKey.typeInteger) { %>1<% } else { %>"existing_id"<% } %>); + <%_ } _%> + <%_ if (dtoMapstruct) { _%> + <%= dtoClass %> <%= dtoInstance %> = <%= entityInstance %>Mapper.toDto(<%= persistInstance %>); + <%_ } _%> + + int databaseSizeBeforeCreate = <%= entityInstance %>Repository.findAll()<%= callListBlock %>.size(); + <%_ if (searchEngineElasticsearch) { _%> + int searchDatabaseSizeBefore = IterableUtil.sizeOf(<%= entityInstance %>SearchRepository.findAll()<%= callListBlock %>); + <%_ } _%> + + // An entity with an existing ID cannot be created, so this API call must fail + <%_ if (reactive) { _%> + webTestClient.post().uri(ENTITY_API_URL) + .contentType(MediaType.APPLICATION_JSON) + .bodyValue(TestUtil.convertObjectToJsonBytes(<%= restInstance %>)) + .exchange() + .expectStatus().isBadRequest(); + <%_ } else { _%> + rest<%= entityClass %>MockMvc.perform(post(ENTITY_API_URL)<% if (authenticationUsesCsrf) { %>.with(csrf())<% }%> + .contentType(MediaType.APPLICATION_JSON) + .content(TestUtil.convertObjectToJsonBytes(<%= restInstance %>))) + .andExpect(status().isBadRequest()); + <%_ } _%> + + // Validate the <%= entityClass %> in the database + <%_ if (databaseTypeCouchbase) { _%> + SecurityContextHolder.setContext(TestSecurityContextHolder.getContext()); + <%_ } _%> + List<<%= persistClass %>> <%= entityInstance %>List = <%= entityInstance %>Repository.findAll()<%= callListBlock %>; + assertThat(<%= entityInstance %>List).hasSize(databaseSizeBeforeCreate); + <%_ if (searchEngineElasticsearch) { _%> + int searchDatabaseSizeAfter = IterableUtil.sizeOf(<%= entityInstance %>SearchRepository.findAll()<%= callListBlock %>); + assertThat(searchDatabaseSizeAfter).isEqualTo(searchDatabaseSizeBefore); + <%_ } _%> + } + + <%= getTestPrefix %><%= transactionalAnnotation %> + @WithMockUser(username = "user", authorities = {"<%= getDefaultRole %>"}) + void create<%= entityClass %>WithExistingId_with_<%= getDefaultRole %>() throws Exception { + create<%= entityClass %>WithExistingId(); + } + + <%_ if (databaseTypeSql && isUsingMapsId) { _%> + void update<%= entityClass %>MapsIdAssociationWithNewIdIsAllowed() throws Exception { + // Initialize the database + <%= entityInstance %>Repository.<%= saveMethod %>(<%= persistInstance %>)<%= callBlock %>; + <%_ let alreadyGeneratedEntities = []; _%> + int databaseSizeBeforeCreate = <%= entityInstance %>Repository.findAll()<%= callListBlock %>.size(); + <%_ if (searchEngineElasticsearch) { _%> + int searchDatabaseSizeBefore = IterableUtil.sizeOf(<%= entityInstance %>SearchRepository.findAll()<%= callListBlock %>); + <%_ } _%> + <%_ for (relationship of relationships) { + const otherEntityName = relationship.otherEntityName; + const otherEntityNameCapitalized = relationship.otherEntityNameCapitalized; + const mapsIdUse = relationship.id; + if (mapsIdUse) { _%> + // Add a new parent entity + <%_ if (alreadyGeneratedEntities.indexOf(otherEntityName) == -1) { _%> + <%= relationship.otherEntity.persistClass %> <%= otherEntityName %> = <%= otherEntityNameCapitalized %>ResourceIT.create<% if (!relationship.otherEntityUser) { _%>Updated<%_ } %>Entity(<% if (databaseTypeSql) { %>em<% } %>); + <%_ if (databaseTypeSql && !reactive) { _%> + em.persist(<%= otherEntityName %>); + em.flush(); + <%_ } _%> + <%_ } _%> + <%_ alreadyGeneratedEntities.push(otherEntityName) _%> + <%_ } _%> + <%_ break; } _%> + + // Load the <%= entityInstance %> + <%= persistClass %> updated<%= persistClass %> = <%= entityInstance %>Repository.findById(<%= persistInstance %>.get<%= primaryKey.nameCapitalized %>())<%= reactive ? '.block()' : '.orElseThrow()' %>; + assertThat(updated<%= persistClass %>).isNotNull(); + <%_ if (databaseTypeSql && !reactive) { _%> + // Disconnect from session so that the updates on updated<%= persistClass %> are not directly saved in db + em.detach(updated<%= persistClass %>); + <%_ } _%> + + // Update the <%= mapsIdEntity %> with new association value + updated<%= persistClass %>.set<%= mapsIdEntity %>(<%= alreadyGeneratedEntities.pop() %>); + <%_ if (dtoMapstruct) { _%> + <%= dtoClass %> updated<%= dtoClass %> = <%= entityInstance %>Mapper.toDto(updated<%= persistClass %>); + assertThat(updated<%= dtoClass %>).isNotNull(); + <%_ } _%> + + // Update the entity + <%_ if (reactive) { _%> + webTestClient.put().uri(ENTITY_API_URL_ID, <%_ if (dtoMapstruct) { _%>updated<%= dtoClass %> <%_ } else { _%> updated<%= persistClass %> <%_ } _%>.get<%= primaryKey.nameCapitalized %>()) + .contentType(MediaType.APPLICATION_JSON) + .bodyValue(TestUtil.convertObjectToJsonBytes(<%_ if (dtoMapstruct) { _%>updated<%= dtoClass %> <%_ } else { _%> updated<%= persistClass %> <%_ } _%>)) + .exchange() + .expectStatus().isOk(); + <%_ } else { _%> + rest<%= entityClass %>MockMvc.perform(put(ENTITY_API_URL_ID, <%_ if (dtoMapstruct) { _%>updated<%= dtoClass %> <%_ } else { _%> updated<%= persistClass %> <%_ } _%>.get<%= primaryKey.nameCapitalized %>())<% if (authenticationUsesCsrf) { %>.with(csrf())<% }%> + .contentType(MediaType.APPLICATION_JSON) + .content(TestUtil.convertObjectToJsonBytes(<%_ if (dtoMapstruct) { _%>updated<%= dtoClass %> <%_ } else { _%> updated<%= persistClass %> <%_ } _%>))) + .andExpect(status().isOk()); + <%_ } _%> + + // Validate the <%= entityClass %> in the database + <%_ if (databaseTypeCouchbase) { _%> + SecurityContextHolder.setContext(TestSecurityContextHolder.getContext()); + <%_ } _%> + List<<%= persistClass %>> <%= entityInstance %>List = <%= entityInstance %>Repository.findAll()<%= callListBlock %>; + assertThat(<%= entityInstance %>List).hasSize(databaseSizeBeforeCreate); + <%= persistClass %> test<%= entityClass %> = <%= entityInstance %>List.get(<%= entityInstance %>List.size() - 1); + + // Validate the id for MapsId, the ids must be same + // Uncomment the following line for assertion. However, please note that there is a known issue and uncommenting will fail the test. + // Please look at https://github.com/jhipster/generator-jhipster/issues/9100. You can modify this test as necessary. + // assertThat(test<%= entityClass %>.get<%= primaryKey.nameCapitalized %>()).isEqualTo(test<%= entityClass %>.get<%= mapsIdEntity %>().get<%= primaryKey.nameCapitalized %>()); + <%_ if (searchEngineElasticsearch) { _%> + int searchDatabaseSizeAfter = IterableUtil.sizeOf(<%= entityInstance %>SearchRepository.findAll()<%= callListBlock %>); + assertThat(searchDatabaseSizeAfter).isEqualTo(searchDatabaseSizeBefore); + <%_ } _%> + } + <%_ allowedPutRoles.forEach(secRole => { _%> + + <%= putTestPrefix %><%= transactionalAnnotation %> + @WithMockUser(username = "user", authorities = {"<%= secRole %>"}) + void update<%= entityClass %>MapsIdAssociationWithNewId_with_<%= secRole %>() throws Exception { + update<%= entityClass %>MapsIdAssociationWithNewIdIsAllowed(); + } + <%_ }); _%> + + void update<%= entityClass %>MapsIdAssociationWithNewIdIsForbidden() throws Exception { + // Initialize the database + <%= entityInstance %>Repository.<%= saveMethod %>(<%= persistInstance %>)<%= callBlock %>; + <%_ alreadyGeneratedEntities = []; _%> + int databaseSizeBeforeCreate = <%= entityInstance %>Repository.findAll()<%= callListBlock %>.size(); + <%_ if (searchEngineElasticsearch) { _%> + int searchDatabaseSizeBefore = IterableUtil.sizeOf(<%= entityInstance %>SearchRepository.findAll()<%= callListBlock %>); + <%_ } _%> + <%_ for (relationship of relationships) { + const otherEntityName = relationship.otherEntityName; + const otherEntityNameCapitalized = relationship.otherEntityNameCapitalized; + const mapsIdUse = relationship.id; + if (mapsIdUse) { _%> + // Add a new parent entity + <%_ if (alreadyGeneratedEntities.indexOf(otherEntityName) == -1) { _%> + <%= relationship.otherEntity.persistClass %> <%= otherEntityName %> = <%= otherEntityNameCapitalized %>ResourceIT.create<% if (!relationship.otherEntityUser) { _%>Updated<%_ } %>Entity(<% if (databaseTypeSql) { %>em<% } %>); + <%_ if (databaseTypeSql && !reactive) { _%> + em.persist(<%= otherEntityName %>); + em.flush(); + <%_ } _%> + <%_ } _%> + <%_ alreadyGeneratedEntities.push(otherEntityName) _%> + <%_ } _%> + <%_ break; } _%> + + // Load the <%= entityInstance %> + <%= persistClass %> updated<%= persistClass %> = <%= entityInstance %>Repository.findById(<%= persistInstance %>.get<%= primaryKey.nameCapitalized %>())<%= reactive ? '.block()' : '.orElseThrow()' %>; + assertThat(updated<%= persistClass %>).isNotNull(); + <%_ if (databaseTypeSql && !reactive) { _%> + // Disconnect from session so that the updates on updated<%= persistClass %> are not directly saved in db + em.detach(updated<%= persistClass %>); + <%_ } _%> + + // Update the <%= mapsIdEntity %> with new association value + updated<%= persistClass %>.set<%= mapsIdEntity %>(<%= alreadyGeneratedEntities.pop() %>); + <%_ if (dtoMapstruct) { _%> + <%= dtoClass %> updated<%= dtoClass %> = <%= entityInstance %>Mapper.toDto(updated<%= persistClass %>); + assertThat(updated<%= dtoClass %>).isNotNull(); + <%_ } _%> + + // Update the entity + <%_ if (reactive) { _%> + webTestClient.put().uri(ENTITY_API_URL_ID, <%_ if (dtoMapstruct) { _%>updated<%= dtoClass %> <%_ } else { _%> updated<%= persistClass %> <%_ } _%>.get<%= primaryKey.nameCapitalized %>()) + .contentType(MediaType.APPLICATION_JSON) + .bodyValue(TestUtil.convertObjectToJsonBytes(<%_ if (dtoMapstruct) { _%>updated<%= dtoClass %> <%_ } else { _%> updated<%= persistClass %> <%_ } _%>)) + .exchange() + .expectStatus().isForbidden(); + <%_ } else { _%> + rest<%= entityClass %>MockMvc.perform(put(ENTITY_API_URL_ID, <%_ if (dtoMapstruct) { _%>updated<%= dtoClass %> <%_ } else { _%> updated<%= persistClass %> <%_ } _%>.get<%= primaryKey.nameCapitalized %>())<% if (authenticationUsesCsrf) { %>.with(csrf())<% }%> + .contentType(MediaType.APPLICATION_JSON) + .content(TestUtil.convertObjectToJsonBytes(<%_ if (dtoMapstruct) { _%>updated<%= dtoClass %> <%_ } else { _%> updated<%= persistClass %> <%_ } _%>))) + .andExpect(status().isForbidden()); + <%_ } _%> + + // Validate the <%= entityClass %> in the database + <%_ if (databaseTypeCouchbase) { _%> + SecurityContextHolder.setContext(TestSecurityContextHolder.getContext()); + <%_ } _%> + List<<%= persistClass %>> <%= entityInstance %>List = <%= entityInstance %>Repository.findAll()<%= callListBlock %>; + assertThat(<%= entityInstance %>List).hasSize(databaseSizeBeforeCreate); + <%= persistClass %> test<%= entityClass %> = <%= entityInstance %>List.get(<%= entityInstance %>List.size() - 1); + + // Validate the id for MapsId, the ids must be same + // Uncomment the following line for assertion. However, please note that there is a known issue and uncommenting will fail the test. + // Please look at https://github.com/jhipster/generator-jhipster/issues/9100. You can modify this test as necessary. + // assertThat(test<%= entityClass %>.get<%= primaryKey.nameCapitalized %>()).isEqualTo(test<%= entityClass %>.get<%= mapsIdEntity %>().get<%= primaryKey.nameCapitalized %>()); + <%_ if (searchEngineElasticsearch) { _%> + int searchDatabaseSizeAfter = IterableUtil.sizeOf(<%= entityInstance %>SearchRepository.findAll()<%= callListBlock %>); + assertThat(searchDatabaseSizeAfter).isEqualTo(searchDatabaseSizeBefore); + <%_ } _%> + } + <%_ security.forbiddenRoles.put.forEach(secRole => { _%> + + @Test<%= transactionalAnnotation %> + @WithMockUser(username = "user", authorities = {"<%= secRole %>"}) + void update<%= entityClass %>MapsIdAssociationWithNewId_with_<%= secRole %>() throws Exception { + update<%= entityClass %>MapsIdAssociationWithNewIdIsForbidden(); + } + <%_ }); _%> + + <%_ } _%> + <%_ for (field of fieldsToTest) { _%> + <%_ if (field.fieldValidate) { + let required = false; + if (!field.fieldTypeBytes && field.fieldValidate && field.fieldValidationRequired) { + required = true; + } _%> + <%_ if (required) { _%> + + void check<%= field.fieldInJavaBeanMethod %>IsRequired() throws Exception { + int databaseSizeBeforeTest = <%= entityInstance %>Repository.findAll()<%= callListBlock %>.size(); + <%_ if (searchEngineElasticsearch) { _%> + int searchDatabaseSizeBefore = IterableUtil.sizeOf(<%= entityInstance %>SearchRepository.findAll()<%= callListBlock %>); + <%_ } _%> + // set the field null + <%= persistInstance %>.set<%= field.fieldInJavaBeanMethod %>(null); + + // Create the <%= entityClass %>, which fails.<% if (dtoMapstruct) { %> + <%= dtoClass %> <%= dtoInstance %> = <%= entityInstance %>Mapper.toDto(<%= persistInstance %>);<% } %> + + + <%_ if (reactive) { _%> + webTestClient.post().uri(ENTITY_API_URL) + .contentType(MediaType.APPLICATION_JSON) + .bodyValue(TestUtil.convertObjectToJsonBytes(<%= restInstance %>)) + .exchange() + .expectStatus().isBadRequest(); + <%_ } else { _%> + rest<%= entityClass %>MockMvc.perform(post(ENTITY_API_URL)<% if (authenticationUsesCsrf) { %>.with(csrf())<% }%> + .contentType(MediaType.APPLICATION_JSON) + .content(TestUtil.convertObjectToJsonBytes(<%= restInstance %>))) + .andExpect(status().isBadRequest()); + <%_ } _%> + + <%_ if (databaseTypeCouchbase) { _%> + SecurityContextHolder.setContext(TestSecurityContextHolder.getContext()); + <%_ } _%> + List<<%= persistClass %>> <%= entityInstance %>List = <%= entityInstance %>Repository.findAll()<%= callListBlock %>; + assertThat(<%= entityInstance %>List).hasSize(databaseSizeBeforeTest); + <%_ if (searchEngineElasticsearch) { _%> + int searchDatabaseSizeAfter = IterableUtil.sizeOf(<%= entityInstance %>SearchRepository.findAll()<%= callListBlock %>); + assertThat(searchDatabaseSizeAfter).isEqualTo(searchDatabaseSizeBefore); + <%_ } _%> + + } + + <%= postTestPrefix %><%= transactionalAnnotation %> + @WithMockUser(username = "user", authorities = {"<%= postDefaultRole %>"}) + void check<%= field.fieldInJavaBeanMethod %>IsRequired_with_<%= postDefaultRole %>() throws Exception { + check<%= field.fieldInJavaBeanMethod %>IsRequired(); + } + <%_ } _%> + <%_ } _%> + <%_ } _%> +<%_ } _%> +<%_ if (!jpaMetamodelFiltering && reactive && paginationNo) { _%> + + void getAll<%= entityClassPlural %>AsStreamIsAllowed() { + // Initialize the database + <%_ if (!primaryKey.derived) { _%> + <%_ for (field of primaryKey.fields.filter(f => !f.autoGenerateByRepository)) { _%> + <%= persistInstance %>.set<%= field.fieldNameCapitalized %>(<%- this.getJavaValueGeneratorForType(field.fieldType) %>); + <%_ } _%> + <%_ } _%> + <%= entityInstance %>Repository.save(<%= persistInstance %>)<%= callBlock %>; + + List<<%= persistClass %>> <%= entityInstance %>List = webTestClient.get().uri(ENTITY_API_URL) + .accept(MediaType.APPLICATION_NDJSON) + .exchange() + .expectStatus().isOk() + .expectHeader().contentTypeCompatibleWith(MediaType.APPLICATION_NDJSON) + .returnResult(<%= restClass %>.class) + .getResponseBody() + <%_ if (dtoMapstruct) { _%> + .map(<%= entityInstance %>Mapper::toEntity) + <%_ } _%> + .filter(<%= persistInstance %>::equals) + .collectList() + .block(Duration.ofSeconds(5)); + + assertThat(<%= entityInstance %>List).isNotNull(); + assertThat(<%= entityInstance %>List).hasSize(1); + <%= persistClass %> test<%= entityClass %> = <%= entityInstance %>List.get(0); + <%_ for (const field of fieldsToTest) { + if (field.fieldTypeZonedDateTime) { _%> + assertThat(test<%= entityClass %>.get<%= field.fieldInJavaBeanMethod %>()).isEqualTo(<%= 'DEFAULT_' + field.fieldNameUnderscored.toUpperCase() %>); + <%_ } else if ((field.fieldTypeBinary) && !field.blobContentTypeText) { _%> + assertThat(test<%= entityClass %>.get<%= field.fieldInJavaBeanMethod %>()).isEqualTo(<%= 'DEFAULT_' + field.fieldNameUnderscored.toUpperCase() %>); + assertThat(test<%= entityClass %>.get<%= field.fieldInJavaBeanMethod %>ContentType()).isEqualTo(<%= 'DEFAULT_' + field.fieldNameUnderscored.toUpperCase() %>_CONTENT_TYPE); + <%_ } else if (field.fieldTypeBigDecimal) { _%> + assertThat(test<%= entityClass %>.get<%= field.fieldInJavaBeanMethod %>()).isEqualByComparingTo(<%= 'DEFAULT_' + field.fieldNameUnderscored.toUpperCase() %>); + <%_ } else { _%> + assertThat(test<%= entityClass %>.get<%= field.fieldInJavaBeanMethod %>()).isEqualTo(<%= 'DEFAULT_' + field.fieldNameUnderscored.toUpperCase() %>); + <%_ } + } _%> + } +<%_ allowedGetRoles.forEach(secRole => { _%> + + <%= getTestPrefix %><%= transactionalAnnotation %> + @WithMockUser(username = "user", authorities = {"<%= secRole %>"}) + void get<%= entityClass %>AllAsStream_with_<%= secRole %>() throws Exception { + get<%= entityClass %>AllAsStreamIsAllowed(); + } +<%_ }); _%> + + void getAll<%= entityClassPlural %>AsStreamIsForbidden() { + // Initialize the database + <%_ if (!primaryKey.derived) { _%> + <%_ for (field of primaryKey.fields.filter(f => !f.autoGenerateByRepository)) { _%> + <%= persistInstance %>.set<%= field.fieldNameCapitalized %>(<%- this.getJavaValueGeneratorForType(field.fieldType) %>); + <%_ } _%> + <%_ } _%> + <%= entityInstance %>Repository.save(<%= persistInstance %>)<%= callBlock %>; + + List<<%= persistClass %>> <%= entityInstance %>List = webTestClient.get().uri(ENTITY_API_URL) + .accept(MediaType.APPLICATION_NDJSON) + .exchange() + .expectStatus().isOk() + .expectHeader().contentTypeCompatibleWith(MediaType.APPLICATION_NDJSON) + .returnResult(<%= restClass %>.class) + .getResponseBody() + <%_ if (dtoMapstruct) { _%> + .map(<%= entityInstance %>Mapper::toEntity) + <%_ } _%> + .filter(<%= persistInstance %>::equals) + .collectList() + .block(Duration.ofSeconds(5)); + + assertThat(<%= entityInstance %>List).isNotNull(); + assertThat(<%= entityInstance %>List).hasSize(1); + <%= persistClass %> test<%= entityClass %> = <%= entityInstance %>List.get(0); + <%_ for (const field of fieldsToTest) { + if (field.fieldTypeZonedDateTime) { _%> + assertThat(test<%= entityClass %>.get<%= field.fieldInJavaBeanMethod %>()).isEqualTo(<%= 'DEFAULT_' + field.fieldNameUnderscored.toUpperCase() %>); + <%_ } else if ((field.fieldTypeBinary) && !field.blobContentTypeText) { _%> + assertThat(test<%= entityClass %>.get<%= field.fieldInJavaBeanMethod %>()).isEqualTo(<%= 'DEFAULT_' + field.fieldNameUnderscored.toUpperCase() %>); + assertThat(test<%= entityClass %>.get<%= field.fieldInJavaBeanMethod %>ContentType()).isEqualTo(<%= 'DEFAULT_' + field.fieldNameUnderscored.toUpperCase() %>_CONTENT_TYPE); + <%_ } else if (field.fieldTypeBigDecimal) { _%> + assertThat(test<%= entityClass %>.get<%= field.fieldInJavaBeanMethod %>()).isEqualByComparingTo(<%= 'DEFAULT_' + field.fieldNameUnderscored.toUpperCase() %>); + <%_ } else { _%> + assertThat(test<%= entityClass %>.get<%= field.fieldInJavaBeanMethod %>()).isEqualTo(<%= 'DEFAULT_' + field.fieldNameUnderscored.toUpperCase() %>); + <%_ } + } _%> + } +<%_ security.forbiddenRoles.get.forEach(secRole => { _%> + + @Test<%= transactionalAnnotation %> + @WithMockUser(username = "user", authorities = {"<%= secRole %>"}) + void get<%= entityClass %>AllAsStream_with_<%= secRole %>() throws Exception { + get<%= entityClass %>AllAsStreamIsForbidden(); + } +<%_ }); _%> + +<%_ } _%> + + void getAll<%= entityClassPlural %>IsAllowed() <% if (!reactive) { %>throws Exception <% } %>{ + // Initialize the database +<%_ if (!primaryKey.derived) { _%> + <%_ for (field of primaryKey.fields.filter(f => !f.autoGenerateByRepository)) { _%> + <%= persistInstance %>.set<%= field.fieldNameCapitalized %>(<%- this.getJavaValueGeneratorForType(field.fieldType) %>); + <%_ } _%> +<%_ } _%> + <%= entityInstance %>Repository.<%= saveMethod %>(<%= persistInstance %>)<%= callBlock %>; + + // Get all the <%= entityInstance %>List +<%_ if (reactive) { _%> + webTestClient.get().uri(ENTITY_API_URL + "?sort=<%= primaryKey.name %>,desc") + .accept(MediaType.APPLICATION_JSON) + .exchange() + .expectStatus().isOk() + .expectHeader().contentType(MediaType.APPLICATION_JSON) + .expectBody() +<%_ } else { _%> + rest<%= entityClass %>MockMvc.perform(get(ENTITY_API_URL<% if (!databaseTypeCassandra) { %> + "?sort=<%= primaryKey.name %>,desc"<% } %>)) + .andExpect(status().isOk()) + .andExpect(content().contentType(MediaType.APPLICATION_JSON_VALUE)) +<%_ } _%> +<%_ if (databaseTypeSql || databaseTypeMongodb || databaseTypeCouchbase || databaseTypeCassandra) { _%> + <%= !reactive ? '.andExpect(' : '.' %>jsonPath("$.[*].<%= primaryKey.name %>").value(hasItem(<%= idValue %>))<%= !reactive ? ')' : '' %><%_ } _%><% for (field of fieldsToTest) { %> + <%_ if (field.fieldTypeBinary && !field.blobContentTypeText) { _%> + <%= !reactive ? '.andExpect(' : '.' %>jsonPath("$.[*].<%= field.fieldName %>ContentType").value(hasItem(<%= 'DEFAULT_' + field.fieldNameUnderscored.toUpperCase() %>_CONTENT_TYPE))<%= !reactive ? ')' : '' %> + <%_ } _%> + <%= !reactive ? '.andExpect(' : '.' %>jsonPath("$.[*].<%= field.fieldName %>").value(hasItem(<% + if (field.fieldTypeBinary && !field.blobContentTypeText) { %>Base64Utils.encodeToString(<% } else + if (field.fieldTypeZonedDateTime) { %>sameInstant(<% } else + if (field.fieldTypeBigDecimal) { %>sameNumber(<% } %><%= 'DEFAULT_' + field.fieldNameUnderscored.toUpperCase() %><% + if (field.fieldTypeBinary && !field.blobContentTypeText) { %><% + if (databaseTypeCassandra) { %>.array()<% } %>)<% } else + if (field.fieldTypeInteger) { %><% } else + if (field.fieldTypeLong) { %>.intValue()<% } else + if (field.fieldTypeFloat || field.fieldTypeDouble) { %>.doubleValue()<% } else + if (field.fieldTypeBigDecimal) { %>)<% } else + if (field.fieldTypeBoolean) { %>.booleanValue()<% } else + if (field.fieldTypeZonedDateTime) { %>)<% } else + if (!field.fieldTypeString) { %>.toString()<% } %>))<%= !reactive ? ')' : '' %><%_ } _%>; + } +<%_ allowedGetRoles.forEach(secRole => { _%> + + <%= getTestPrefix %><%= transactionalAnnotation %> + @WithMockUser(username = "user", authorities = {"<%= secRole %>"}) + void getAll<%= entityClassPlural %>IsAllowed_with_<%= secRole %>() throws Exception { + getAll<%= entityClassPlural %>IsAllowed(); + } +<%_ }); _%> + + void getAll<%= entityClassPlural %>IsForbidden() <% if (!reactive) { %>throws Exception <% } %>{ + // Initialize the database +<%_ if (!primaryKey.derived) { _%> + <%_ for (field of primaryKey.fields.filter(f => !f.autoGenerateByRepository)) { _%> + <%= persistInstance %>.set<%= field.fieldNameCapitalized %>(<%- this.getJavaValueGeneratorForType(field.fieldType) %>); + <%_ } _%> +<%_ } _%> + <%= entityInstance %>Repository.<%= saveMethod %>(<%= persistInstance %>)<%= callBlock %>; + + // Get all the <%= entityInstance %>List +<%_ if (reactive) { _%> + webTestClient.get().uri(ENTITY_API_URL + "?sort=<%= primaryKey.name %>,desc") + .accept(MediaType.APPLICATION_JSON) + .exchange() + .expectStatus().isForbidden(); +<%_ } else { _%> + rest<%= entityClass %>MockMvc.perform(get(ENTITY_API_URL<% if (!databaseTypeCassandra) { %> + "?sort=<%= primaryKey.name %>,desc"<% } %>)) + .andExpect(status().isForbidden()); +<%_ } _%> + } +<%_ security.forbiddenRoles.get.forEach(secRole => { _%> + + @Test<%= transactionalAnnotation %> + @WithMockUser(username = "user", authorities = {"<%= secRole %>"}) + void getAll<%= entityClassPlural %>IsForbidden_with_<%= secRole %>() throws Exception { + getAll<%= entityClassPlural %>IsForbidden(); + } +<%_ }); _%> +<% if (implementsEagerLoadApis && !databaseTypeNeo4j && !databaseTypeCouchbase) { %> + + @SuppressWarnings({"unchecked"}) + void getAll<%= entityClassPlural %>WithEagerRelationshipsIsEnabled() <% if (!reactive) { %>throws Exception <% } %>{ + <%_ if (!serviceNo) { _%> + when(<%= entityInstance %>ServiceMock.findAllWithEagerRelationships(any())).thenReturn(<% if (reactive) { %>Flux.empty()<% } else { %>new PageImpl(new ArrayList<>())<% }%>); + <%_ } else { _%> + when(<%= entityInstance %>RepositoryMock.findAllWithEagerRelationships(any())).thenReturn(<% if (reactive) { %>Flux.empty()<% } else { %>new PageImpl(new ArrayList<>())<% }%>); + <%_ } _%> + + <%_ if (reactive) { _%> + webTestClient.get().uri(ENTITY_API_URL + "?eagerload=true") + .exchange() + .expectStatus().isOk(); + <%_ } else { _%> + rest<%= entityClass %>MockMvc.perform(get(ENTITY_API_URL + "?eagerload=true")) + .andExpect(status().isOk()); + <%_ } _%> + + <%_ if (!serviceNo) { _%> + verify(<%= entityInstance %>ServiceMock, times(1)).findAllWithEagerRelationships(any()); + <%_ } else { _%> + verify(<%= entityInstance %>RepositoryMock, times(1)).findAllWithEagerRelationships(any()); + <%_ } _%> + } + + @SuppressWarnings({"unchecked"}) + void getAll<%= entityClassPlural %>WithEagerRelationshipsIsNotEnabled() <% if (!reactive) { %>throws Exception <% } %>{ + <%_ if (!serviceNo) { _%> + when(<%= entityInstance %>ServiceMock.findAllWithEagerRelationships(any())).thenReturn(<% if (reactive) { %>Flux.empty()<% } else { %>new PageImpl(new ArrayList<>())<% }%>); + <%_ } else { _%> + when(<%= entityInstance %>RepositoryMock.findAllWithEagerRelationships(any())).thenReturn(<% if (reactive) { %>Flux.empty()<% } else { %>new PageImpl(new ArrayList<>())<% }%>); + <%_ } _%> + + <%_ if (reactive) { _%> + webTestClient.get().uri(ENTITY_API_URL + "?eagerload=false") + .exchange() + .expectStatus().isOk(); + <%_ } else { _%> + rest<%= entityClass %>MockMvc.perform(get(ENTITY_API_URL + "?eagerload=false")) + .andExpect(status().isOk()); + <%_ } _%> + verify(<%= entityInstance %>RepositoryMock, times(1)).findAll<% if (reactive) { %>WithEagerRelationships(any()<% } else { %>(any(Pageable.class)<% } %>); + } +<%_ } _%> + + void get<%= entityClass %>IsAllowed() <% if (!reactive) { %>throws Exception <% } %>{ + // Initialize the database +<%_ if (!primaryKey.derived) { _%> + <%_ for (field of primaryKey.fields.filter(f => !f.autoGenerateByRepository)) { _%> + <%= persistInstance %>.set<%= field.fieldNameCapitalized %>(<%- this.getJavaValueGeneratorForType(field.fieldType) %>); + <%_ } _%> +<%_ } _%> + <%= entityInstance %>Repository.<%= saveMethod %>(<%= persistInstance %>)<%= callBlock %>; + + // Get the <%= entityInstance %> +<%_ if (reactive) { _%> + webTestClient.get().uri(ENTITY_API_URL_ID, <%= persistInstance %>.get<%= primaryKey.nameCapitalized %>()) + .accept(MediaType.APPLICATION_JSON) + .exchange() + .expectStatus().isOk() + .expectHeader().contentType(MediaType.APPLICATION_JSON) + .expectBody() +<%_ } else { _%> + rest<%= entityClass %>MockMvc.perform(get(ENTITY_API_URL_ID, <%= persistInstance %>.get<%= primaryKey.nameCapitalized %>())) + .andExpect(status().isOk()) + .andExpect(content().contentType(MediaType.APPLICATION_JSON_VALUE)) +<%_ } _%> +<%_ if (databaseTypeSql || databaseTypeMongodb || databaseTypeCouchbase || databaseTypeCassandra) { _%> + <%= !reactive ? '.andExpect(' : '.' %>jsonPath("$.<%= primaryKey.name %>").value(<%= reactive ? 'is(' : '' %><%= idValue %>))<%_ } _%><% for (field of fieldsToTest) { %> + <%_ if (field.fieldTypeBinary && !field.blobContentTypeText) { _%> + <%= !reactive ? '.andExpect(' : '.' %>jsonPath("$.<%= field.fieldName %>ContentType").value(<%= reactive ? 'is(' : '' %><%= 'DEFAULT_' + field.fieldNameUnderscored.toUpperCase() %>_CONTENT_TYPE)) + <%_ } _%> + <%= !reactive ? '.andExpect(' : '.' %>jsonPath("$.<%= field.fieldName %>").value(<%= reactive ? 'is(' : '' %><% + if (field.fieldTypeBinary && !field.blobContentTypeText) { %>Base64Utils.encodeToString(<% } else + if (field.fieldTypeZonedDateTime) { %>sameInstant(<% } else + if (field.fieldTypeBigDecimal) { %>sameNumber(<% } %><%= 'DEFAULT_' + field.fieldNameUnderscored.toUpperCase() %><% + if (field.fieldTypeBinary && !field.blobContentTypeText) { %><% + if (databaseTypeCassandra) { %>.array()<% } %>)<% } else + if (field.fieldTypeInteger) { %><% } else + if (field.fieldTypeLong) { %>.intValue()<% } else + if (field.fieldTypeFloat || field.fieldTypeDouble) { %>.doubleValue()<% } else + if (field.fieldTypeBigDecimal) { %>)<% } else + if (field.fieldTypeBoolean) { %>.booleanValue()<% } else + if (field.fieldTypeZonedDateTime) { %>)<% } else + if (!field.fieldTypeString) { %>.toString()<% } %>))<%_ } _%>; + } +<%_ allowedGetRoles.forEach(secRole => { _%> + + <%= getTestPrefix %><%= transactionalAnnotation %> + @WithMockUser(username = "user", authorities = {"<%= secRole %>"}) + void get<%= entityClass %>IsAllowed_with_<%= secRole %>() throws Exception { + get<%= entityClass %>IsAllowed(); + } +<%_ }); _%> + + void get<%= entityClass %>IsForbidden() <% if (!reactive) { %>throws Exception <% } %>{ + // Initialize the database +<%_ if (!primaryKey.derived) { _%> + <%_ for (field of primaryKey.fields.filter(f => !f.autoGenerateByRepository)) { _%> + <%= persistInstance %>.set<%= field.fieldNameCapitalized %>(<%- this.getJavaValueGeneratorForType(field.fieldType) %>); + <%_ } _%> +<%_ } _%> + <%= entityInstance %>Repository.<%= saveMethod %>(<%= persistInstance %>)<%= callBlock %>; + + // Get the <%= entityInstance %> +<%_ if (reactive) { _%> + webTestClient.get().uri(ENTITY_API_URL_ID, <%= persistInstance %>.get<%= primaryKey.nameCapitalized %>()) + .accept(MediaType.APPLICATION_JSON) + .exchange() + .expectStatus().isForbidden(); +<%_ } else { _%> + rest<%= entityClass %>MockMvc.perform(get(ENTITY_API_URL_ID, <%= persistInstance %>.get<%= primaryKey.nameCapitalized %>())) + .andExpect(status().isForbidden()); +<%_ } _%> + } +<%_ security.forbiddenRoles.get.forEach(secRole => { _%> + + @Test<%= transactionalAnnotation %> + @WithMockUser(username = "user", authorities = {"<%= secRole %>"}) + void get<%= entityClass %>IsForbidden_with_<%= secRole %>() throws Exception { + get<%= entityClass %>IsForbidden(); + } +<%_ }); _%> +<%_ if (jpaMetamodelFiltering) { _%> + + void get<%= entityClassPlural %>ByIdFiltering() <% if (!reactive) { %>throws Exception <% } %>{ + // Initialize the database + <%= entityInstance %>Repository.<%= saveMethod %>(<%= persistInstance %>)<%= callBlock %>; + + <%= primaryKey.type %> id = <%= persistInstance %>.get<%= primaryKey.nameCapitalized %>(); + + default<%= entityClass %>ShouldBeFound("<%= primaryKey.name %>.equals=" + id); + default<%= entityClass %>ShouldNotBeFound("<%= primaryKey.name %>.notEquals=" + id); + + <%_ if (primaryKey.typeLong || primaryKey.typeInteger) { _%> + default<%= entityClass %>ShouldBeFound("<%= primaryKey.name %>.greaterThanOrEqual=" + id); + default<%= entityClass %>ShouldNotBeFound("<%= primaryKey.name %>.greaterThan=" + id); + + default<%= entityClass %>ShouldBeFound("<%= primaryKey.name %>.lessThanOrEqual=" + id); + default<%= entityClass %>ShouldNotBeFound("<%= primaryKey.name %>.lessThan=" + id); + <%_ } _%> + } + + <%= getTestPrefix %><%= transactionalAnnotation %> + @WithMockUser(username = "user", authorities = {"<%= getDefaultRole %>"}) + void get<%= entityClassPlural %>ByIdFiltering_with_<%= getDefaultRole %>() throws Exception { + get<%= entityClassPlural %>ByIdFiltering(); + } + + <%_ fieldsToTest.forEach((searchBy) => { /* we can't filter by all the fields. */_%> + <%_ if (searchBy.filterableField) { _%> + + <%= getTestPrefix%><%= transactionalAnnotation %> + @WithMockUser(username = "user", authorities = {"<%= getDefaultRole %>"}) + void getAll<%= entityClassPlural %>By<%= searchBy.fieldInJavaBeanMethod %>IsEqualToSomething() <% if (!reactive) { %>throws Exception <% } %>{ + // Initialize the database + <%= entityInstance %>Repository.<%= saveMethod %>(<%= persistInstance %>)<%= callBlock %>; + + // Get all the <%= entityInstance %>List where <%= searchBy.fieldName %> equals to <%= 'DEFAULT_' + searchBy.fieldNameUnderscored.toUpperCase() %> + default<%= entityClass %>ShouldBeFound("<%= searchBy.fieldName %>.equals=" + <%= 'DEFAULT_' + searchBy.fieldNameUnderscored.toUpperCase() %>); + + // Get all the <%= entityInstance %>List where <%= searchBy.fieldName %> equals to <%= 'UPDATED_' + searchBy.fieldNameUnderscored.toUpperCase() %> + default<%= entityClass %>ShouldNotBeFound("<%= searchBy.fieldName %>.equals=" + <%= 'UPDATED_' + searchBy.fieldNameUnderscored.toUpperCase() %>); + } + + <%= getTestPrefix%><%= transactionalAnnotation %> + @WithMockUser(username = "user", authorities = {"<%= getDefaultRole %>"}) + void getAll<%= entityClassPlural %>By<%= searchBy.fieldInJavaBeanMethod %>IsInShouldWork() <% if (!reactive) { %>throws Exception <% } %>{ + // Initialize the database + <%= entityInstance %>Repository.<%= saveMethod %>(<%= persistInstance %>)<%= callBlock %>; + + // Get all the <%= entityInstance %>List where <%= searchBy.fieldName %> in <%= 'DEFAULT_' + searchBy.fieldNameUnderscored.toUpperCase() %> or <%= 'UPDATED_' + searchBy.fieldNameUnderscored.toUpperCase() %> + default<%= entityClass %>ShouldBeFound("<%= searchBy.fieldName %>.in=" + <%= 'DEFAULT_' + searchBy.fieldNameUnderscored.toUpperCase() %> + "," + <%= 'UPDATED_' + searchBy.fieldNameUnderscored.toUpperCase() %>); + + // Get all the <%= entityInstance %>List where <%= searchBy.fieldName %> equals to <%= 'UPDATED_' + searchBy.fieldNameUnderscored.toUpperCase() %> + default<%= entityClass %>ShouldNotBeFound("<%= searchBy.fieldName %>.in=" + <%= 'UPDATED_' + searchBy.fieldNameUnderscored.toUpperCase() %>); + } + + <%= getTestPrefix%><%= transactionalAnnotation %> + @WithMockUser(username = "user", authorities = {"<%= getDefaultRole %>"}) + void getAll<%= entityClassPlural %>By<%= searchBy.fieldInJavaBeanMethod %>IsNullOrNotNull() <% if (!reactive) { %>throws Exception <% } %>{ + // Initialize the database + <%= entityInstance %>Repository.<%= saveMethod %>(<%= persistInstance %>)<%= callBlock %>; + + // Get all the <%= entityInstance %>List where <%= searchBy.fieldName %> is not null + default<%= entityClass %>ShouldBeFound("<%= searchBy.fieldName %>.specified=true"); + + // Get all the <%= entityInstance %>List where <%= searchBy.fieldName %> is null + default<%= entityClass %>ShouldNotBeFound("<%= searchBy.fieldName %>.specified=false"); + } + <%_ } _%> + <%_ if (searchBy.fieldTypeString) { _%> + + <%= getTestPrefix%><%= transactionalAnnotation %> + @WithMockUser(username = "user", authorities = {"<%= getDefaultRole %>"}) + void getAll<%= entityClassPlural %>By<%= searchBy.fieldInJavaBeanMethod %>ContainsSomething() <% if (!reactive) { %>throws Exception <% } %>{ + // Initialize the database + <%= entityInstance %>Repository.<%= saveMethod %>(<%= persistInstance %>)<%= callBlock %>; + + // Get all the <%= entityInstance %>List where <%= searchBy.fieldName %> contains <%= 'DEFAULT_' + searchBy.fieldNameUnderscored.toUpperCase() %> + default<%= entityClass %>ShouldBeFound("<%= searchBy.fieldName %>.contains=" + <%= 'DEFAULT_' + searchBy.fieldNameUnderscored.toUpperCase() %>); + + // Get all the <%= entityInstance %>List where <%= searchBy.fieldName %> contains <%= 'UPDATED_' + searchBy.fieldNameUnderscored.toUpperCase() %> + default<%= entityClass %>ShouldNotBeFound("<%= searchBy.fieldName %>.contains=" + <%= 'UPDATED_' + searchBy.fieldNameUnderscored.toUpperCase() %>); + } + + <%= getTestPrefix%><%= transactionalAnnotation %> + @WithMockUser(username = "user", authorities = {"<%= getDefaultRole %>"}) + void getAll<%= entityClassPlural %>By<%= searchBy.fieldInJavaBeanMethod %>NotContainsSomething() <% if (!reactive) { %>throws Exception <% } %>{ + // Initialize the database + <%= entityInstance %>Repository.<%= saveMethod %>(<%= persistInstance %>)<%= callBlock %>; + + // Get all the <%= entityInstance %>List where <%= searchBy.fieldName %> does not contain <%= 'DEFAULT_' + searchBy.fieldNameUnderscored.toUpperCase() %> + default<%= entityClass %>ShouldNotBeFound("<%= searchBy.fieldName %>.doesNotContain=" + <%= 'DEFAULT_' + searchBy.fieldNameUnderscored.toUpperCase() %>); + + // Get all the <%= entityInstance %>List where <%= searchBy.fieldName %> does not contain <%= 'UPDATED_' + searchBy.fieldNameUnderscored.toUpperCase() %> + default<%= entityClass %>ShouldBeFound("<%= searchBy.fieldName %>.doesNotContain=" + <%= 'UPDATED_' + searchBy.fieldNameUnderscored.toUpperCase() %>); + } + + <%_ } + // the range criteria + if (searchBy.fieldTypeNumeric || searchBy.fieldTypeDuration || searchBy.fieldTypeLocalDate || searchBy.fieldTypeZonedDateTime) { + var defaultValue = 'DEFAULT_' + searchBy.fieldNameUnderscored.toUpperCase(); + var biggerValue = 'UPDATED_' + searchBy.fieldNameUnderscored.toUpperCase(); + var smallerValue = 'SMALLER_' + searchBy.fieldNameUnderscored.toUpperCase(); + if (searchBy.fieldValidate && searchBy.fieldValidationMax) { + // if maximum is specified the updated variable is smaller than the default one! + if (searchBy.fieldTypeBigDecimal) { + biggerValue = '(' + defaultValue + '.add(BigDecimal.ONE))'; + } else if (searchBy.fieldTypeDuration || searchBy.fieldTypeLocalDate || searchBy.fieldTypeZonedDateTime) { + biggerValue = '(' + defaultValue + '.plus(1, ChronoUnit.DAYS))'; + } else { + biggerValue = '(' + defaultValue + ' + 1)'; + } + } + _%> + + <%= getTestPrefix%><%= transactionalAnnotation %> + @WithMockUser(username = "user", authorities = {"<%= getDefaultRole %>"}) + void getAll<%= entityClassPlural %>By<%= searchBy.fieldInJavaBeanMethod %>IsGreaterThanOrEqualToSomething() <% if (!reactive) { %>throws Exception <% } %>{ + // Initialize the database + <%= entityInstance %>Repository.<%= saveMethod %>(<%= persistInstance %>)<%= callBlock %>; + + // Get all the <%= entityInstance %>List where <%= searchBy.fieldName %> is greater than or equal to <%= defaultValue %> + default<%= entityClass %>ShouldBeFound("<%= searchBy.fieldName %>.greaterThanOrEqual=" + <%= defaultValue %>); + + // Get all the <%= entityInstance %>List where <%= searchBy.fieldName %> is greater than or equal to <%= biggerValue %> + default<%= entityClass %>ShouldNotBeFound("<%= searchBy.fieldName %>.greaterThanOrEqual=" + <%= biggerValue %>); + } + + <%= getTestPrefix%><%= transactionalAnnotation %> + @WithMockUser(username = "user", authorities = {"<%= getDefaultRole %>"}) + void getAll<%= entityClassPlural %>By<%= searchBy.fieldInJavaBeanMethod %>IsLessThanOrEqualToSomething() <% if (!reactive) { %>throws Exception <% } %>{ + // Initialize the database + <%= entityInstance %>Repository.<%= saveMethod %>(<%= persistInstance %>)<%= callBlock %>; + + // Get all the <%= entityInstance %>List where <%= searchBy.fieldName %> is less than or equal to <%= defaultValue %> + default<%= entityClass %>ShouldBeFound("<%= searchBy.fieldName %>.lessThanOrEqual=" + <%= defaultValue %>); + + // Get all the <%= entityInstance %>List where <%= searchBy.fieldName %> is less than or equal to <%= smallerValue %> + default<%= entityClass %>ShouldNotBeFound("<%= searchBy.fieldName %>.lessThanOrEqual=" + <%= smallerValue %>); + } + + <%= getTestPrefix%><%= transactionalAnnotation %> + @WithMockUser(username = "user", authorities = {"<%= getDefaultRole %>"}) + void getAll<%= entityClassPlural %>By<%= searchBy.fieldInJavaBeanMethod %>IsLessThanSomething() <% if (!reactive) { %>throws Exception <% } %>{ + // Initialize the database + <%= entityInstance %>Repository.<%= saveMethod %>(<%= persistInstance %>)<%= callBlock %>; + + // Get all the <%= entityInstance %>List where <%= searchBy.fieldName %> is less than <%= defaultValue %> + default<%= entityClass %>ShouldNotBeFound("<%= searchBy.fieldName %>.lessThan=" + <%= defaultValue %>); + + // Get all the <%= entityInstance %>List where <%= searchBy.fieldName %> is less than <%= biggerValue %> + default<%= entityClass %>ShouldBeFound("<%= searchBy.fieldName %>.lessThan=" + <%= biggerValue %>); + } + + <%= getTestPrefix%><%= transactionalAnnotation %> + @WithMockUser(username = "user", authorities = {"<%= getDefaultRole %>"}) + void getAll<%= entityClassPlural %>By<%= searchBy.fieldInJavaBeanMethod %>IsGreaterThanSomething() <% if (!reactive) { %>throws Exception <% } %>{ + // Initialize the database + <%= entityInstance %>Repository.<%= saveMethod %>(<%= persistInstance %>)<%= callBlock %>; + + // Get all the <%= entityInstance %>List where <%= searchBy.fieldName %> is greater than <%= defaultValue %> + default<%= entityClass %>ShouldNotBeFound("<%= searchBy.fieldName %>.greaterThan=" + <%= defaultValue %>); + + // Get all the <%= entityInstance %>List where <%= searchBy.fieldName %> is greater than <%= smallerValue %> + default<%= entityClass %>ShouldBeFound("<%= searchBy.fieldName %>.greaterThan=" + <%= smallerValue %>); + } + + <%_ } _%> + <%_ }); _%> + <%_ filterTestableRelationships.forEach((relationship) => { _%> + + <%= getTestPrefix%><%= transactionalAnnotation %> + @WithMockUser(username = "user", authorities = {"<%= getDefaultRole %>"}) + void getAll<%= entityClassPlural %>By<%= relationship.relationshipNameCapitalized %>IsEqualToSomething() <% if (!reactive) { %>throws Exception <% } %>{ + <%_ if ((relationship.relationshipValidate && relationship.relationshipOneToOne) || relationship.id) { _%> + // Get already existing entity + <%= relationship.otherEntity.persistClass %> <%= relationship.relationshipFieldName %> = <%= persistInstance %>.get<%= relationship.relationshipNameCapitalized %>(); + <%_ } else { _%> + <%_ if (databaseTypeSql && !reactive) { _%> + <%= relationship.otherEntity.persistClass %> <%= relationship.relationshipFieldName %>; + if (TestUtil.findAll(em, <%= relationship.otherEntity.persistClass %>.class).isEmpty()) { + <%= entityInstance %>Repository.saveAndFlush(<%= persistInstance %>); + <%= relationship.relationshipFieldName %> = <%= createEntityPrefix %><%= relationship.otherEntityNameCapitalized %>ResourceIT.createEntity(em); + } else { + <%= relationship.relationshipFieldName %> = TestUtil.findAll(em, <%= relationship.otherEntity.persistClass %>.class).get(0); + } + <%_ } else { _%> + <%= relationship.otherEntity.persistClass %> <%= relationship.relationshipFieldName %> = <%= relationship.otherEntityNameCapitalized %>ResourceIT.createEntity(em); + <%_ } _%> + <%_ if(reactive) { _%> + <%= relationship.otherEntity.persistInstance %>Repository.<%= saveMethod %>(<%= relationship.relationshipFieldName %>)<%= callBlock %>; + <%_ } else { _%> + em.persist(<%= relationship.relationshipFieldName %>); + em.flush(); + <%_ } _%> + <%_ if (!reactive && (relationship.relationshipManyToMany || relationship.relationshipOneToMany)) { _%> + <%= persistInstance %>.add<%= relationship.relationshipNameCapitalized %>(<%= relationship.relationshipFieldName %>); + <%_ } else if (!reactive) { _%> + <%= persistInstance %>.set<%= relationship.relationshipNameCapitalized %>(<%= relationship.relationshipFieldName %>); + <%_ if (!relationship.ownerSide) { _%> + <%= relationship.relationshipFieldName %>.set<%= relationship.otherEntityRelationshipNameCapitalized %>(<%= persistInstance %>); + <%_ } _%> + <%_ } else { _%> + <%= relationship.otherEntity.primaryKey.type %> <%= relationship.relationshipFieldName %>Id = <%= relationship.relationshipFieldName %>.get<%= relationship.otherEntity.primaryKey.nameCapitalized %>(); + <%= persistInstance %>.set<%= relationship.relationshipNameCapitalized %>Id(<%= relationship.relationshipFieldName %>Id); + <%= entityInstance %>Repository.<%= saveMethod %>(<%= persistInstance %>)<%= callBlock %>; + <%_ } _%> + <%_ } _%> + <%_ if(!reactive) { _%> + <%= entityInstance %>Repository.saveAndFlush(<%= persistInstance %>); + <%= relationship.otherEntity.primaryKey.type %> <%= relationship.relationshipFieldName %>Id = <%= relationship.relationshipFieldName %>.get<%= relationship.otherEntity.primaryKey.nameCapitalized %>(); + <%_ } _%> + // Get all the <%= entityInstance %>List where <%= relationship.relationshipFieldName %> equals to <%= relationship.relationshipFieldName %>Id + default<%= entityClass %>ShouldBeFound("<%= relationship.relationshipFieldName %>Id.equals=" + <%= relationship.relationshipFieldName %>Id); + + <%_ + const initInvalidPrimaryKey = { + 'String' : '"invalid-id"', + 'Long' : '(' + relationship.relationshipFieldName + 'Id + 1)', + 'UUID' : 'UUID.randomUUID()' + }[relationship.otherEntity.primaryKey.type]; + _%> + // Get all the <%= entityInstance %>List where <%= relationship.relationshipFieldName %> equals to <%- initInvalidPrimaryKey %> + default<%= entityClass %>ShouldNotBeFound("<%= relationship.relationshipFieldName %>Id.equals=" + <%- initInvalidPrimaryKey %>); + } + + <%_ }); _%> + /** + * Executes the search, and checks that the default entity is returned. + */ + <%_ if (reactive) { _%> + private void default<%= entityClass %>ShouldBeFound(String filter) { + webTestClient.get().uri(ENTITY_API_URL + "?sort=<%= primaryKey.name %>,desc&" + filter) + .accept(MediaType.APPLICATION_JSON) + .exchange() + .expectStatus().isOk() + .expectHeader().contentType(MediaType.APPLICATION_JSON) + .expectBody() + .jsonPath("$.[*].<%= primaryKey.name %>").value(hasItem(<%= idValue %>))<% for (field of fieldsToTest) { %> + <%_ if (field.fieldTypeBinary && !field.blobContentTypeText) { _%> + .jsonPath("$.[*].<%= field.fieldName %>ContentType").value(hasItem(<%= 'DEFAULT_' + field.fieldNameUnderscored.toUpperCase() %>_CONTENT_TYPE)) + <%_ } _%> + .jsonPath("$.[*].<%= field.fieldName %>").value(hasItem(<% if + (field.fieldTypeBinary && !field.blobContentTypeText) { %>Base64Utils.encodeToString(<% } else + if (field.fieldTypeZonedDateTime) { %>sameInstant(<% } else + if (field.fieldTypeBigDecimal) { %>sameNumber(<% } %><%= 'DEFAULT_' + field.fieldNameUnderscored.toUpperCase() %><% + if (field.fieldTypeBinary && !field.blobContentTypeText) { %><% if (databaseTypeCassandra) { %>.array()<% } %>)<% } else + if (field.fieldTypeInteger) { %><% } else + if (field.fieldTypeLong) { %>.intValue()<% } else + if (field.fieldTypeFloat || field.fieldTypeDouble) { %>.doubleValue()<% } else + if (field.fieldTypeBigDecimal) { %>)<% } else + if (field.fieldTypeBoolean) { %>.booleanValue()<% } else + if (field.fieldTypeZonedDateTime) { %>)<% } else + if (!field.fieldTypeString) { %>.toString()<% } %>))<%_ } _%>; + + // Check, that the count call also returns 1 + webTestClient.get().uri(ENTITY_API_URL + "/count?sort=<%= primaryKey.name %>,desc&" + filter) + .accept(MediaType.APPLICATION_JSON) + .exchange() + .expectStatus().isOk() + .expectHeader().contentType(MediaType.APPLICATION_JSON) + .expectBody() + <%_ if(reactive) { _%> + .jsonPath("$") + .value(is(1)); + <%_ } else { _%> + .json("1"); + <%_ } _%> + } + <%_ } else { _%> + private void default<%= entityClass %>ShouldBeFound(String filter) throws Exception { + rest<%= entityClass %>MockMvc.perform(get(ENTITY_API_URL + "?sort=<%= primaryKey.name %>,desc&" + filter)) + .andExpect(status().isOk()) + .andExpect(content().contentType(MediaType.APPLICATION_JSON_VALUE)) + <%_ + const primaryKeyConversion = { + 'Long' : '.intValue()', + 'UUID' : '.toString()' + }[primaryKey.type] || ''; + _%> + .andExpect(jsonPath("$.[*].<%= primaryKey.name %>").value(hasItem(<%= persistInstance %>.get<%= primaryKey.nameCapitalized %>()<%= primaryKeyConversion %>)))<% fieldsToTest.forEach((field) => { %> + <%_ if (field.fieldTypeBinary && !field.blobContentTypeText) { _%> + .andExpect(jsonPath("$.[*].<%= field.fieldName %>ContentType").value(hasItem(<%= 'DEFAULT_' + field.fieldNameUnderscored.toUpperCase() %>_CONTENT_TYPE))) + <%_ } _%> + .andExpect(jsonPath("$.[*].<%= field.fieldName %>").value(hasItem(<% + if (field.fieldTypeBinary && !field.blobContentTypeText) { %>Base64Utils.encodeToString(<% } else + if (field.fieldTypeZonedDateTime) { %>sameInstant(<% } else + if (field.fieldTypeBigDecimal) { %>sameNumber(<% } %><%= 'DEFAULT_' + field.fieldNameUnderscored.toUpperCase() %><% + if (field.fieldTypeBinary && !field.blobContentTypeText) { %><% + if (databaseTypeCassandra) { %>.array()<% } %>)<% } else + if (field.fieldTypeInteger) { %><% } else + if (field.fieldTypeLong) { %>.intValue()<% } else + if (field.fieldTypeFloat || field.fieldTypeDouble) { %>.doubleValue()<% } else + if (field.fieldTypeBigDecimal) { %>)<% } else + if (field.fieldTypeBoolean) { %>.booleanValue()<% } else + if (field.fieldTypeZonedDateTime) { %>)<% } else + if (!field.fieldTypeString) { %>.toString()<% } %>)))<% }); %>; + + // Check, that the count call also returns 1 + rest<%= entityClass %>MockMvc.perform(get(ENTITY_API_URL + "/count?sort=<%= primaryKey.name %>,desc&" + filter)) + .andExpect(status().isOk()) + .andExpect(content().contentType(MediaType.APPLICATION_JSON_VALUE)) + .andExpect(content().string("1")); + } + <%_ } _%> + + /** + * Executes the search, and checks that the default entity is not returned. + */ + <%_ if (reactive) { _%> + private void default<%= entityClass %>ShouldNotBeFound(String filter) { + webTestClient.get().uri(ENTITY_API_URL + "?sort=<%= primaryKey.name %>,desc&" + filter) + .accept(MediaType.APPLICATION_JSON) + .exchange() + .expectStatus().isOk() + .expectHeader().contentType(MediaType.APPLICATION_JSON) + .expectBody() + .jsonPath("$").isArray() + .jsonPath("$").isEmpty(); + + // Check, that the count call also returns 0 + webTestClient.get().uri(ENTITY_API_URL + "/count?sort=<%= primaryKey.name %>,desc&" + filter) + .accept(MediaType.APPLICATION_JSON) + .exchange() + .expectStatus().isOk() + .expectHeader().contentType(MediaType.APPLICATION_JSON) + .expectBody() + <%_ if(reactive) { _%> + .jsonPath("$") + .value(is(0)); + <%_ } else { _%> + .json("0"); + <%_ } _%> + } + <%_ } else { _%> + private void default<%= entityClass %>ShouldNotBeFound(String filter) throws Exception { + rest<%= entityClass %>MockMvc.perform(get(ENTITY_API_URL + "?sort=<%= primaryKey.name %>,desc&" + filter)) + .andExpect(status().isOk()) + .andExpect(content().contentType(MediaType.APPLICATION_JSON_VALUE)) + .andExpect(jsonPath("$").isArray()) + .andExpect(jsonPath("$").isEmpty()); + + // Check, that the count call also returns 0 + rest<%= entityClass %>MockMvc.perform(get(ENTITY_API_URL + "/count?sort=<%= primaryKey.name %>,desc&" + filter)) + .andExpect(status().isOk()) + .andExpect(content().contentType(MediaType.APPLICATION_JSON_VALUE)) + .andExpect(content().string("0")); + } + <%_ } _%> + +<%_ } _%> + <%= getTestPrefix %><%= transactionalAnnotation %> + @WithMockUser(username = "user", authorities = {"<%= getDefaultRole %>"}) + void getNonExisting<%= entityClass %>() <% if (!reactive) { %>throws Exception <% } %>{ + // Get the <%= entityInstance %> +<%_ if (reactive) { _%> + webTestClient.get().uri(ENTITY_API_URL_ID, <% if (primaryKey.typeInteger) { %>Integer.MAX_VALUE<% } else if (primaryKey.typeLong || primaryKey.typeString) { %>Long.MAX_VALUE<% } else if (primaryKey.typeUUID) { %>UUID.randomUUID().toString()<% } %>) + .accept(MediaType.APPLICATION_PROBLEM_JSON) + .exchange() + .expectStatus().isNotFound(); +<%_ } else { _%> + rest<%= entityClass %>MockMvc.perform(get(ENTITY_API_URL_ID, <% if (primaryKey.typeInteger) { %>Integer.MAX_VALUE<% } else if (primaryKey.typeLong || primaryKey.typeString) { %>Long.MAX_VALUE<% } else if (primaryKey.typeUUID) { %>UUID.randomUUID().toString()<% } %>)) + .andExpect(status().isNotFound()); +<%_ } _%> + } +<%_ if (!readOnly) { _%> + + <%= putTestPrefix %><%= transactionalAnnotation %> + @WithMockUser(username = "user", authorities = {"<%= putDefaultRole %>"}) + void putExisting<%= entityClass %>() throws Exception { + // Initialize the database + <%_ if (!primaryKey.derived) { _%> + <%_ for (field of primaryKey.fields.filter(f => !f.autoGenerateByRepository)) { _%> + <%= persistInstance %>.set<%= field.fieldNameCapitalized %>(<%- this.getJavaValueGeneratorForType(field.fieldType) %>); + <%_ } _%> + <%_ } _%> + <%= entityInstance %>Repository.<%= saveMethod %>(<%= persistInstance %>)<%= callBlock %>; + + int databaseSizeBeforeUpdate = <%= entityInstance %>Repository.findAll()<%= callListBlock %>.size(); + <%_ if (searchEngineElasticsearch) { _%> + <%= entityInstance %>SearchRepository.save(<%= persistInstance %>)<%= callBlock %>; + int searchDatabaseSizeBefore = IterableUtil.sizeOf(<%= entityInstance %>SearchRepository.findAll()<%= callListBlock %>); + <%_ } _%> + + // Update the <%= entityInstance %> + <%= persistClass %> updated<%= persistClass %> = <%= entityInstance %>Repository.findById(<%= persistInstance %>.get<%= primaryKey.nameCapitalized %>())<%= reactive ? '.block()' : '.orElseThrow()' %>; + <%_ if (databaseTypeSql && !reactive) { _%> + // Disconnect from session so that the updates on updated<%= persistClass %> are not directly saved in db + em.detach(updated<%= persistClass %>); + <%_ } _%> + <%_ if (fluentMethods && fieldsToTest.length > 0) { _%> + updated<%= persistClass %><% for (field of fieldsToTest) { %> + .<%= field.fieldName %>(<%= 'UPDATED_' + field.fieldNameUnderscored.toUpperCase() %>)<% if (field.fieldTypeBinary && !field.blobContentTypeText) { %> + .<%= field.fieldName %>ContentType(<%= 'UPDATED_' + field.fieldNameUnderscored.toUpperCase() %>_CONTENT_TYPE)<% } %><% } %>; + <%_ } else { _%> + <%_ for (field of fieldsToTest) { _%> + updated<%= persistClass %>.set<%= field.fieldInJavaBeanMethod %>(<%= 'UPDATED_' + field.fieldNameUnderscored.toUpperCase() %>); + <%_ if (field.fieldTypeBinary && !field.blobContentTypeText) { _%> + updated<%= persistClass %>.set<%= field.fieldInJavaBeanMethod %>ContentType(<%= 'UPDATED_' + field.fieldNameUnderscored.toUpperCase() %>_CONTENT_TYPE); + <%_ } _%> + <%_ } _%> + <%_ } _%> + <%_ if (dtoMapstruct) { _%> + <%= dtoClass %> <%= dtoInstance %> = <%= entityInstance %>Mapper.toDto(updated<%= persistClass %>); + <%_ } _%> + + <%_ if (reactive) { _%> + webTestClient.put().uri(ENTITY_API_URL_ID, <%= (dtoMapstruct ? dtoInstance : 'updated' + persistClass) %>.get<%= primaryKey.nameCapitalized %>()) + .contentType(MediaType.APPLICATION_JSON) + .bodyValue(TestUtil.convertObjectToJsonBytes(<%= (dtoMapstruct ? dtoInstance : 'updated' + persistClass) %>)) + .exchange() + .expectStatus().isOk(); + <%_ } else { _%> + rest<%= entityClass %>MockMvc.perform(put(ENTITY_API_URL_ID, <%= (dtoMapstruct ? dtoInstance : 'updated' + persistClass) %>.get<%= primaryKey.nameCapitalized %>())<% if (authenticationUsesCsrf) { %>.with(csrf())<% } %> + .contentType(MediaType.APPLICATION_JSON) + .content(TestUtil.convertObjectToJsonBytes(<%= (dtoMapstruct ? dtoInstance : 'updated' + persistClass) %>))) + .andExpect(status().isOk()); + <%_ } _%> + + // Validate the <%= entityClass %> in the database + <%_ if (databaseTypeCouchbase) { _%> + SecurityContextHolder.setContext(TestSecurityContextHolder.getContext()); + <%_ } _%> + List<<%= persistClass %>> <%= entityInstance %>List = <%= entityInstance %>Repository.findAll()<%= callListBlock %>; + assertThat(<%= entityInstance %>List).hasSize(databaseSizeBeforeUpdate); + <%= persistClass %> test<%= entityClass %> = <%= entityInstance %>List.get(<%= entityInstance %>List.size() - 1); + <%_ for (const field of fieldsToTest) { _%> + <%_ if (field.fieldTypeZonedDateTime) { _%> + assertThat(test<%= entityClass %>.get<%= field.fieldInJavaBeanMethod %>()).isEqualTo(<%= 'UPDATED_' + field.fieldNameUnderscored.toUpperCase() %>); + <%_ } else if (field.fieldTypeBinary && !field.blobContentTypeText) { _%> + assertThat(test<%= entityClass %>.get<%= field.fieldInJavaBeanMethod %>()).isEqualTo(<%= 'UPDATED_' + field.fieldNameUnderscored.toUpperCase() %>); + assertThat(test<%= entityClass %>.get<%= field.fieldInJavaBeanMethod %>ContentType()).isEqualTo(<%= 'UPDATED_' + field.fieldNameUnderscored.toUpperCase() %>_CONTENT_TYPE); + <%_ } else if (field.fieldTypeBigDecimal) { _%> + assertThat(test<%= entityClass %>.get<%= field.fieldInJavaBeanMethod %>()).isEqualByComparingTo(<%= 'UPDATED_' + field.fieldNameUnderscored.toUpperCase() %>); + <%_ } else { _%> + assertThat(test<%= entityClass %>.get<%= field.fieldInJavaBeanMethod %>()).isEqualTo(<%= 'UPDATED_' + field.fieldNameUnderscored.toUpperCase() %>); + <%_ } _%> + <%_ } _%> + <%_ if (searchEngineElasticsearch) { _%> + await().atMost(5, TimeUnit.SECONDS).untilAsserted(() -> { + int searchDatabaseSizeAfter = IterableUtil.sizeOf(<%= entityInstance %>SearchRepository.findAll()<%= callListBlock %>); + assertThat(searchDatabaseSizeAfter).isEqualTo(searchDatabaseSizeBefore); + List<<%= persistClass %>> <%= entityInstance %>SearchList = IterableUtils.toList(<%= entityInstance %>SearchRepository.findAll()<%= callListBlock %>); + <%= persistClass %> test<%= entityClass %>Search = <%= entityInstance %>SearchList.get(searchDatabaseSizeAfter - 1); + <%_ for (const field of fieldsToTest) { _%> + <%_ if (field.fieldTypeZonedDateTime) { _%> + assertThat(test<%= entityClass %>Search.get<%= field.fieldInJavaBeanMethod %>()).isEqualTo(<%= 'UPDATED_' + field.fieldNameUnderscored.toUpperCase() %>); + <%_ } else if (field.fieldTypeBinary && !field.blobContentTypeText) { _%> + assertThat(test<%= entityClass %>Search.get<%= field.fieldInJavaBeanMethod %>()).isEqualTo(<%= 'UPDATED_' + field.fieldNameUnderscored.toUpperCase() %>); + assertThat(test<%= entityClass %>Search.get<%= field.fieldInJavaBeanMethod %>ContentType()).isEqualTo(<%= 'UPDATED_' + field.fieldNameUnderscored.toUpperCase() %>_CONTENT_TYPE); + <%_ } else if (field.fieldTypeBigDecimal) { _%> + assertThat(test<%= entityClass %>Search.get<%= field.fieldInJavaBeanMethod %>()).isEqualByComparingTo(<%= 'UPDATED_' + field.fieldNameUnderscored.toUpperCase() %>); + <%_ } else { _%> + assertThat(test<%= entityClass %>Search.get<%= field.fieldInJavaBeanMethod %>()).isEqualTo(<%= 'UPDATED_' + field.fieldNameUnderscored.toUpperCase() %>); + <%_ } _%> + <%_ } _%> + }); + <%_ } _%> + } + + <%= putTestPrefix%><%= transactionalAnnotation %> + @WithMockUser(username = "user", authorities = {"<%= putDefaultRole %>"}) + void putNonExisting<%= entityClass %>() throws Exception { + int databaseSizeBeforeUpdate = <%= entityInstance %>Repository.findAll()<%= callListBlock %>.size(); + <%_ if (searchEngineElasticsearch) { _%> + int searchDatabaseSizeBefore = IterableUtil.sizeOf(<%= entityInstance %>SearchRepository.findAll()<%= callListBlock %>); + <%_ } _%> + <%= persistInstance %>.set<%= primaryKey.nameCapitalized %>(<%- this.getJavaValueGeneratorForType(primaryKey.type) %>); + + <%_ if (dtoMapstruct) { _%> + // Create the <%= entityClass %> + <%= dtoClass %> <%= dtoInstance %> = <%= entityInstance %>Mapper.toDto(<%= persistInstance %>); + + <%_ } _%> + // If the entity doesn't have an ID, it will throw BadRequestAlertException + <%_ if (reactive) { _%> + webTestClient.put().uri(ENTITY_API_URL_ID, <%= restInstance %>.get<%= primaryKey.nameCapitalized %>()) + .contentType(MediaType.APPLICATION_JSON) + .bodyValue(TestUtil.convertObjectToJsonBytes(<%= restInstance %>)) + .exchange() + .expectStatus().isBadRequest(); + <%_ } else { _%> + rest<%= entityClass %>MockMvc.perform(put(ENTITY_API_URL_ID, <%= restInstance %>.get<%= primaryKey.nameCapitalized %>())<% if (authenticationUsesCsrf) { %>.with(csrf())<% }%> + .contentType(MediaType.APPLICATION_JSON) + .content(TestUtil.convertObjectToJsonBytes(<%= restInstance %>))) + .andExpect(status().isBadRequest()); + <%_ } _%> + + // Validate the <%= entityClass %> in the database + <%_ if (databaseTypeCouchbase) { _%> + SecurityContextHolder.setContext(TestSecurityContextHolder.getContext()); + <%_ } _%> + List<<%= persistClass %>> <%= entityInstance %>List = <%= entityInstance %>Repository.findAll()<%= callListBlock %>; + assertThat(<%= entityInstance %>List).hasSize(databaseSizeBeforeUpdate); + <%_ if (searchEngineElasticsearch) { _%> + int searchDatabaseSizeAfter = IterableUtil.sizeOf(<%= entityInstance %>SearchRepository.findAll()<%= callListBlock %>); + assertThat(searchDatabaseSizeAfter).isEqualTo(searchDatabaseSizeBefore); + + <%_ } _%> + } + + <%= putTestPrefix%><%= transactionalAnnotation %> + @WithMockUser(username = "user", authorities = {"<%= putDefaultRole %>"}) + void putWithIdMismatch<%= entityClass %>() throws Exception { + int databaseSizeBeforeUpdate = <%= entityInstance %>Repository.findAll()<%= callListBlock %>.size(); + <%_ if (searchEngineElasticsearch) { _%> + int searchDatabaseSizeBefore = IterableUtil.sizeOf(<%= entityInstance %>SearchRepository.findAll()<%= callListBlock %>); + <%_ } _%> + <%= persistInstance %>.set<%= primaryKey.nameCapitalized %>(<%- this.getJavaValueGeneratorForType(primaryKey.type) %>); + + <%_ if (dtoMapstruct) { _%> + // Create the <%= entityClass %> + <%= dtoClass %> <%= dtoInstance %> = <%= entityInstance %>Mapper.toDto(<%= persistInstance %>); + + <%_ } _%> + // If url ID doesn't match entity ID, it will throw BadRequestAlertException + <%_ if (reactive) { _%> + webTestClient.put().uri(ENTITY_API_URL_ID, <%- this.getJavaValueGeneratorForType(primaryKey.type) %>) + .contentType(MediaType.APPLICATION_JSON) + .bodyValue(TestUtil.convertObjectToJsonBytes(<%= restInstance %>)) + .exchange() + .expectStatus().isBadRequest(); + <%_ } else { _%> + rest<%= entityClass %>MockMvc.perform(put(ENTITY_API_URL_ID, <%- this.getJavaValueGeneratorForType(primaryKey.type) %>)<% if (authenticationUsesCsrf) { %>.with(csrf())<% } %> + .contentType(MediaType.APPLICATION_JSON) + .content(TestUtil.convertObjectToJsonBytes(<%= restInstance %>))) + .andExpect(status().isBadRequest()); + <%_ } _%> + + // Validate the <%= entityClass %> in the database + <%_ if (databaseTypeCouchbase) { _%> + SecurityContextHolder.setContext(TestSecurityContextHolder.getContext()); + <%_ } _%> + List<<%= persistClass %>> <%= entityInstance %>List = <%= entityInstance %>Repository.findAll()<%= callListBlock %>; + assertThat(<%= entityInstance %>List).hasSize(databaseSizeBeforeUpdate); + <%_ if (searchEngineElasticsearch) { _%> + int searchDatabaseSizeAfter = IterableUtil.sizeOf(<%= entityInstance %>SearchRepository.findAll()<%= callListBlock %>); + assertThat(searchDatabaseSizeAfter).isEqualTo(searchDatabaseSizeBefore); + <%_ } _%> + } + + <%= putTestPrefix%><%= transactionalAnnotation %> + @WithMockUser(username = "user", authorities = {"<%= putDefaultRole %>"}) + void putWithMissingIdPathParam<%= entityClass %>() throws Exception { + int databaseSizeBeforeUpdate = <%= entityInstance %>Repository.findAll()<%= callListBlock %>.size(); + <%_ if (searchEngineElasticsearch) { _%> + int searchDatabaseSizeBefore = IterableUtil.sizeOf(<%= entityInstance %>SearchRepository.findAll()<%= callListBlock %>); + <%_ } _%> + <%= persistInstance %>.set<%= primaryKey.nameCapitalized %>(<%- this.getJavaValueGeneratorForType(primaryKey.type) %>); + + <%_ if (dtoMapstruct) { _%> + // Create the <%= entityClass %> + <%= dtoClass %> <%= dtoInstance %> = <%= entityInstance %>Mapper.toDto(<%= persistInstance %>); + + <%_ } _%> + // If url ID doesn't match entity ID, it will throw BadRequestAlertException + <%_ if (reactive) { _%> + webTestClient.put().uri(ENTITY_API_URL) + .contentType(MediaType.APPLICATION_JSON) + .bodyValue(TestUtil.convertObjectToJsonBytes(<%= restInstance %>)) + .exchange() + .expectStatus().isEqualTo(405); + <%_ } else { _%> + rest<%= entityClass %>MockMvc.perform(put(ENTITY_API_URL)<% if (authenticationUsesCsrf) { %>.with(csrf())<% } %> + .contentType(MediaType.APPLICATION_JSON) + .content(TestUtil.convertObjectToJsonBytes(<%= restInstance %>))) + .andExpect(status().isMethodNotAllowed()); + <%_ } _%> + + // Validate the <%= entityClass %> in the database + <%_ if (databaseTypeCouchbase) { _%> + SecurityContextHolder.setContext(TestSecurityContextHolder.getContext()); + <%_ } _%> + List<<%= persistClass %>> <%= entityInstance %>List = <%= entityInstance %>Repository.findAll()<%= callListBlock %>; + assertThat(<%= entityInstance %>List).hasSize(databaseSizeBeforeUpdate); + <%_ if (searchEngineElasticsearch) { _%> + int searchDatabaseSizeAfter = IterableUtil.sizeOf(<%= entityInstance %>SearchRepository.findAll()<%= callListBlock %>); + assertThat(searchDatabaseSizeAfter).isEqualTo(searchDatabaseSizeBefore); + <%_ } _%> + } + + <%_ + const prepareFieldForPatchTest = (field, includeCb) => { + const includeField = includeCb(); + const fieldNameUnderscoreUppercased = field.fieldNameUnderscored.toUpperCase(); + const updateWithValue = includeField ? `UPDATED_${fieldNameUnderscoreUppercased}` : 'null'; + const testWithConstant = includeField ? `UPDATED_${fieldNameUnderscoreUppercased}` : `DEFAULT_${fieldNameUnderscoreUppercased}`; + return { includeField, updateWithValue, testWithConstant, ...field}; + }; + _%> + <%= putTestPrefix%><%= transactionalAnnotation %> + @WithMockUser(username = "user", authorities = {"<%= putDefaultRole %>"}) + void partialUpdate<%= entityClass %>WithPatch() throws Exception { + // Initialize the database + <%_ if (!primaryKey.derived) { _%> + <%_ for (field of primaryKey.fields.filter(f => !f.autoGenerateByRepository)) { _%> + <%= persistInstance %>.set<%= field.fieldNameCapitalized %>(<%- this.getJavaValueGeneratorForType(field.fieldType) %>); + <%_ } _%> + <%_ } _%> + <%= entityInstance %>Repository.<%= saveMethod %>(<%= persistInstance %>)<%= callBlock %>; + + <%_ const fieldsToIncludeInPartialPatchTest = fieldsToTest.map(field => prepareFieldForPatchTest(field, () => faker.datatype.boolean())); _%> +<%- include('/_global_partials_entity_/it_patch_update.partial.java.ejs', {fields: fieldsToIncludeInPartialPatchTest, saveMethod, callBlock, callListBlock}); -%> + } + + <%= putTestPrefix%><%= transactionalAnnotation %> + @WithMockUser(username = "user", authorities = {"<%= putDefaultRole %>"}) + void fullUpdate<%= entityClass %>WithPatch() throws Exception { + // Initialize the database + <%_ if (!primaryKey.derived) { _%> + <%_ for (field of primaryKey.fields.filter(f => !f.autoGenerateByRepository)) { _%> + <%= persistInstance %>.set<%= field.fieldNameCapitalized %>(<%- this.getJavaValueGeneratorForType(field.fieldType) %>); + <%_ } _%> + <%_ } _%> + <%= entityInstance %>Repository.<%= saveMethod %>(<%= persistInstance %>)<%= callBlock %>; + + <% const fieldsToIncludeInFullPatchTest = fieldsToTest.map(field => prepareFieldForPatchTest(field, () => true)); %> +<%- include('/_global_partials_entity_/it_patch_update.partial.java.ejs', {fields: fieldsToIncludeInFullPatchTest, saveMethod, callBlock, callListBlock}); -%> + } + + <%= putTestPrefix%><%= transactionalAnnotation %> + @WithMockUser(username = "user", authorities = {"<%= putDefaultRole %>"}) + void patchNonExisting<%= entityClass %>() throws Exception { + int databaseSizeBeforeUpdate = <%= entityInstance %>Repository.findAll()<%= callListBlock %>.size(); + <%_ if (searchEngineElasticsearch) { _%> + int searchDatabaseSizeBefore = IterableUtil.sizeOf(<%= entityInstance %>SearchRepository.findAll()<%= callListBlock %>); + <%_ } _%> + <%= persistInstance %>.set<%= primaryKey.nameCapitalized %>(<%- this.getJavaValueGeneratorForType(primaryKey.type) %>); + + <%_ if (dtoMapstruct) { _%> + // Create the <%= entityClass %> + <%= dtoClass %> <%= dtoInstance %> = <%= entityInstance %>Mapper.toDto(<%= persistInstance %>); + + <%_ } _%> + // If the entity doesn't have an ID, it will throw BadRequestAlertException + <%_ if (reactive) { _%> + webTestClient.patch().uri(ENTITY_API_URL_ID, <%= restInstance %>.get<%= primaryKey.nameCapitalized %>()) + .contentType(MediaType.valueOf("application/merge-patch+json")) + .bodyValue(TestUtil.convertObjectToJsonBytes(<%= restInstance %>)) + .exchange() + .expectStatus().isBadRequest(); + <%_ } else { _%> + rest<%= entityClass %>MockMvc.perform(patch(ENTITY_API_URL_ID, <%= restInstance %>.get<%= primaryKey.nameCapitalized %>())<% if (authenticationUsesCsrf) { %>.with(csrf())<% }%> + .contentType("application/merge-patch+json") + .content(TestUtil.convertObjectToJsonBytes(<%= restInstance %>))) + .andExpect(status().isBadRequest()); + <%_ } _%> + + // Validate the <%= entityClass %> in the database + <%_ if (databaseTypeCouchbase) { _%> + SecurityContextHolder.setContext(TestSecurityContextHolder.getContext()); + <%_ } _%> + List<<%= persistClass %>> <%= entityInstance %>List = <%= entityInstance %>Repository.findAll()<%= callListBlock %>; + assertThat(<%= entityInstance %>List).hasSize(databaseSizeBeforeUpdate); + <%_ if (searchEngineElasticsearch) { _%> + int searchDatabaseSizeAfter = IterableUtil.sizeOf(<%= entityInstance %>SearchRepository.findAll()<%= callListBlock %>); + assertThat(searchDatabaseSizeAfter).isEqualTo(searchDatabaseSizeBefore); + <%_ } _%> + } + + <%= putTestPrefix%><%= transactionalAnnotation %> + @WithMockUser(username = "user", authorities = {"<%= putDefaultRole %>"}) + void patchWithIdMismatch<%= entityClass %>() throws Exception { + int databaseSizeBeforeUpdate = <%= entityInstance %>Repository.findAll()<%= callListBlock %>.size(); + <%_ if (searchEngineElasticsearch) { _%> + int searchDatabaseSizeBefore = IterableUtil.sizeOf(<%= entityInstance %>SearchRepository.findAll()<%= callListBlock %>); + <%_ } _%> + <%= persistInstance %>.set<%= primaryKey.nameCapitalized %>(<%- this.getJavaValueGeneratorForType(primaryKey.type) %>); + + <%_ if (dtoMapstruct) { _%> + // Create the <%= entityClass %> + <%= dtoClass %> <%= dtoInstance %> = <%= entityInstance %>Mapper.toDto(<%= persistInstance %>); + + <%_ } _%> + // If url ID doesn't match entity ID, it will throw BadRequestAlertException + <%_ if (reactive) { _%> + webTestClient.patch().uri(ENTITY_API_URL_ID, <%- this.getJavaValueGeneratorForType(primaryKey.type) %>) + .contentType(MediaType.valueOf("application/merge-patch+json")) + .bodyValue(TestUtil.convertObjectToJsonBytes(<%= restInstance %>)) + .exchange() + .expectStatus().isBadRequest(); + <%_ } else { _%> + rest<%= entityClass %>MockMvc.perform(patch(ENTITY_API_URL_ID, <%- this.getJavaValueGeneratorForType(primaryKey.type) %>)<% if (authenticationUsesCsrf) { %>.with(csrf())<% } %> + .contentType("application/merge-patch+json") + .content(TestUtil.convertObjectToJsonBytes(<%= restInstance %>))) + .andExpect(status().isBadRequest()); + <%_ } _%> + + // Validate the <%= entityClass %> in the database + <%_ if (databaseTypeCouchbase) { _%> + SecurityContextHolder.setContext(TestSecurityContextHolder.getContext()); + <%_ } _%> + List<<%= persistClass %>> <%= entityInstance %>List = <%= entityInstance %>Repository.findAll()<%= callListBlock %>; + assertThat(<%= entityInstance %>List).hasSize(databaseSizeBeforeUpdate); + <%_ if (searchEngineElasticsearch) { _%> + int searchDatabaseSizeAfter = IterableUtil.sizeOf(<%= entityInstance %>SearchRepository.findAll()<%= callListBlock %>); + assertThat(searchDatabaseSizeAfter).isEqualTo(searchDatabaseSizeBefore); + <%_ } _%> + } + + <%= putTestPrefix%><%= transactionalAnnotation %> + @WithMockUser(username = "user", authorities = {"<%= putDefaultRole %>"}) + void patchWithMissingIdPathParam<%= entityClass %>() throws Exception { + int databaseSizeBeforeUpdate = <%= entityInstance %>Repository.findAll()<%= callListBlock %>.size(); + <%_ if (searchEngineElasticsearch) { _%> + int searchDatabaseSizeBefore = IterableUtil.sizeOf(<%= entityInstance %>SearchRepository.findAll()<%= callListBlock %>); + <%_ } _%> + <%= persistInstance %>.set<%= primaryKey.nameCapitalized %>(<%- this.getJavaValueGeneratorForType(primaryKey.type) %>); + + <%_ if (dtoMapstruct) { _%> + // Create the <%= entityClass %> + <%= dtoClass %> <%= dtoInstance %> = <%= entityInstance %>Mapper.toDto(<%= persistInstance %>); + + <%_ } _%> + // If url ID doesn't match entity ID, it will throw BadRequestAlertException + <%_ if (reactive) { _%> + webTestClient.patch().uri(ENTITY_API_URL) + .contentType(MediaType.valueOf("application/merge-patch+json")) + .bodyValue(TestUtil.convertObjectToJsonBytes(<%= restInstance %>)) + .exchange() + .expectStatus().isEqualTo(405); + <%_ } else { _%> + rest<%= entityClass %>MockMvc.perform(patch(ENTITY_API_URL)<% if (authenticationUsesCsrf) { %>.with(csrf())<% } %> + .contentType("application/merge-patch+json") + .content(TestUtil.convertObjectToJsonBytes(<%= restInstance %>))) + .andExpect(status().isMethodNotAllowed()); + <%_ } _%> + + // Validate the <%= entityClass %> in the database + <%_ if (databaseTypeCouchbase) { _%> + SecurityContextHolder.setContext(TestSecurityContextHolder.getContext()); + <%_ } _%> + List<<%= persistClass %>> <%= entityInstance %>List = <%= entityInstance %>Repository.findAll()<%= callListBlock %>; + assertThat(<%= entityInstance %>List).hasSize(databaseSizeBeforeUpdate); + <%_ if (searchEngineElasticsearch) { _%> + int searchDatabaseSizeAfter = IterableUtil.sizeOf(<%= entityInstance %>SearchRepository.findAll()<%= callListBlock %>); + assertThat(searchDatabaseSizeAfter).isEqualTo(searchDatabaseSizeBefore); + <%_ } _%> + + } + + void delete<%= entityClass %>IsAllowed() <% if (!reactive) { %>throws Exception <% } %>{ + // Initialize the database + <%_ if (!primaryKey.derived) { _%> + <%_ for (field of primaryKey.fields.filter(f => !f.autoGenerateByRepository)) { _%> + <%= persistInstance %>.set<%= primaryKey.nameCapitalized %>(<%- this.getJavaValueGeneratorForType(field.fieldType) %>); + <%_ } _%> + <%_ } _%> + <%= entityInstance %>Repository.<%= saveMethod %>(<%= persistInstance %>)<%= callBlock %>; + <%_ if (searchEngineElasticsearch) { _%> + <%= entityInstance %>Repository.save(<%= persistInstance %>)<%= callBlock %>; + <%= entityInstance %>SearchRepository.save(<%= persistInstance %>)<%= callBlock %>; + <%_ } _%> + + int databaseSizeBeforeDelete = <%= entityInstance %>Repository.findAll()<%= callListBlock %>.size(); + <%_ if (searchEngineElasticsearch) { _%> + int searchDatabaseSizeBefore = IterableUtil.sizeOf(<%= entityInstance %>SearchRepository.findAll()<%= callListBlock %>); + assertThat(searchDatabaseSizeBefore).isEqualTo(databaseSizeBeforeDelete); + <%_ } _%> + + // Delete the <%= entityInstance %> + <%_ if (reactive) { _%> + webTestClient.delete().uri(ENTITY_API_URL_ID, <%= persistInstance %>.get<%= primaryKey.nameCapitalized %>()) + .accept(MediaType.APPLICATION_JSON) + .exchange() + .expectStatus().isNoContent(); + <%_ } else { _%> + rest<%= entityClass %>MockMvc.perform(delete(ENTITY_API_URL_ID, <%= persistInstance %>.get<%= primaryKey.nameCapitalized %>()<% if (primaryKey.typeUUID && databaseTypeSql) { %>.toString()<% } %>)<% if (authenticationUsesCsrf) { %>.with(csrf())<% }%> + .accept(MediaType.APPLICATION_JSON)) + .andExpect(status().isNoContent()); + <%_ } _%> + + // Validate the database contains one less item + <%_ if (databaseTypeCouchbase) { _%> + SecurityContextHolder.setContext(TestSecurityContextHolder.getContext()); + <%_ } _%> + List<<%= persistClass %>> <%= entityInstance %>List = <%= entityInstance %>Repository.findAll()<%= callListBlock %>; + assertThat(<%= entityInstance %>List).hasSize(databaseSizeBeforeDelete - 1); + <%_ if (searchEngineElasticsearch) { _%> + int searchDatabaseSizeAfter = IterableUtil.sizeOf(<%= entityInstance %>SearchRepository.findAll()<%= callListBlock %>); + assertThat(searchDatabaseSizeAfter).isEqualTo(searchDatabaseSizeBefore-1); + <%_ } _%> + + } + <%_ allowedDeleteRoles.forEach(secRole => { _%> + + <%= deleteTestPrefix%><%= transactionalAnnotation %> + @WithMockUser(username = "user", authorities = {"<%= secRole %>"}) + void delete<%= entityClass %>_with_<%= secRole %>() throws Exception { + delete<%= entityClass %>IsAllowed(); + } + <%_ }); _%> + + void delete<%= entityClass %>IsForbidden() <% if (!reactive) { %>throws Exception <% } %>{ + // Initialize the database + <%_ if (!primaryKey.derived) { _%> + <%_ for (field of primaryKey.fields.filter(f => !f.autoGenerateByRepository)) { _%> + <%= persistInstance %>.set<%= primaryKey.nameCapitalized %>(<%- this.getJavaValueGeneratorForType(field.fieldType) %>); + <%_ } _%> + <%_ } _%> + <%= entityInstance %>Repository.<%= saveMethod %>(<%= persistInstance %>)<%= callBlock %>; + <%_ if (searchEngineElasticsearch) { _%> + <%= entityInstance %>Repository.save(<%= persistInstance %>)<%= callBlock %>; + <%= entityInstance %>SearchRepository.save(<%= persistInstance %>)<%= callBlock %>; + <%_ } _%> + + int databaseSizeBeforeDelete = <%= entityInstance %>Repository.findAll()<%= callListBlock %>.size(); + <%_ if (searchEngineElasticsearch) { _%> + int searchDatabaseSizeBefore = IterableUtil.sizeOf(<%= entityInstance %>SearchRepository.findAll()<%= callListBlock %>); + assertThat(searchDatabaseSizeBefore).isEqualTo(databaseSizeBeforeDelete); + <%_ } _%> + + // Delete the <%= entityInstance %> + <%_ if (reactive) { _%> + webTestClient.delete().uri(ENTITY_API_URL_ID, <%= persistInstance %>.get<%= primaryKey.nameCapitalized %>()) + .accept(MediaType.APPLICATION_JSON) + .exchange() + .expectStatus().isForbidden(); + <%_ } else { _%> + rest<%= entityClass %>MockMvc.perform(delete(ENTITY_API_URL_ID, <%= persistInstance %>.get<%= primaryKey.nameCapitalized %>()<% if (primaryKey.typeUUID && databaseTypeSql) { %>.toString()<% } %>)<% if (authenticationUsesCsrf) { %>.with(csrf())<% }%> + .accept(MediaType.APPLICATION_JSON)) + .andExpect(status().isForbidden()); + <%_ } _%> + + // Validate the database contains one less item + <%_ if (databaseTypeCouchbase) { _%> + SecurityContextHolder.setContext(TestSecurityContextHolder.getContext()); + <%_ } _%> + List<<%= persistClass %>> <%= entityInstance %>List = <%= entityInstance %>Repository.findAll()<%= callListBlock %>; + assertThat(<%= entityInstance %>List).hasSize(databaseSizeBeforeDelete); + <%_ if (searchEngineElasticsearch) { _%> + int searchDatabaseSizeAfter = IterableUtil.sizeOf(<%= entityInstance %>SearchRepository.findAll()<%= callListBlock %>); + assertThat(searchDatabaseSizeAfter).isEqualTo(searchDatabaseSizeBefore); + <%_ } _%> + + } + <%_ security.forbiddenRoles.delete.forEach(secRole => { _%> + + @Test<%= transactionalAnnotation %> + @WithMockUser(username = "user", authorities = {"<%= secRole %>"}) + void delete<%= entityClass %>_with_<%= secRole %>() throws Exception { + delete<%= entityClass %>IsForbidden(); + } + <%_ }); _%> +<%_ } _%> +<%_ if (searchEngineAny) { _%> + + void search<%= entityClass %>IsAllowed() <% if (!reactive) { %>throws Exception <% } %>{ + // Initialize the database + <%_ if (!primaryKey.derived) { _%> + <%_ for (field of primaryKey.fields.filter(f => !f.autoGenerateByRepository)) { _%> + <%= persistInstance %>.set<%= field.fieldNameCapitalized %>(<%- this.getJavaValueGeneratorForType(field.fieldType) %>); + <%_ } _%> + <%_ } _%> + <%= persistInstance %> = <%= entityInstance %>Repository.<%= saveMethod %>(<%= persistInstance %>)<%= callBlock %>; + <%_ if (searchEngineElasticsearch) { _%> + <%= entityInstance %>SearchRepository.save(<%= persistInstance %>)<%= callBlock %>; + <%_ } else if (searchEngineCouchbase) { _%> + // Wait for the <%= entityInstance %> to be indexed + TestUtil.retryUntilNotEmpty(() -> <%= entityInstance %>Repository.search("id:" + <%= entityInstance %>.get<%= primaryKey.nameCapitalized %>())<% if (reactive) { %>.collectList().block()<% } %>); + <%_ } _%> + + // Search the <%= entityInstance %> + <%_ if (reactive) { _%> + webTestClient.get().uri(ENTITY_SEARCH_API_URL + "?query=id:" + <%= persistInstance %>.get<%= primaryKey.nameCapitalized %>()) + .exchange() + .expectStatus().isOk() + .expectHeader().contentType(MediaType.APPLICATION_JSON) + .expectBody() + <%_ } else { _%> + rest<%= entityClass %>MockMvc.perform(get(ENTITY_SEARCH_API_URL + "?query=id:" + <%= persistInstance %>.get<%= primaryKey.nameCapitalized %>())) + .andExpect(status().isOk()) + .andExpect(content().contentType(MediaType.APPLICATION_JSON_VALUE)) + <%_ } _%> + <%_ if (databaseTypeSql || databaseTypeMongodb || databaseTypeCouchbase || databaseTypeCassandra) { _%> + <%= !reactive ? '.andExpect(' : '.' %>jsonPath("$.[*].<%= primaryKey.name %>").value(hasItem(<%= idValue %>))<%= !reactive ? ')' : '' %><%_ } _%><% for (field of fieldsToTest) { %> + <%_ if (field.fieldTypeBinary && !field.blobContentTypeText) { _%> + <%= !reactive ? '.andExpect(' : '.' %>jsonPath("$.[*].<%= field.fieldName %>ContentType").value(hasItem(<%= 'DEFAULT_' + field.fieldNameUnderscored.toUpperCase() %>_CONTENT_TYPE))<%= !reactive ? ')' : '' %> + <%_ } _%> + <%= !reactive ? '.andExpect(' : '.' %>jsonPath("$.[*].<%= field.fieldName %>").value(hasItem(<% + if (field.fieldTypeBinary && !field.blobContentTypeText) { %>Base64Utils.encodeToString(<% } else + if (field.fieldTypeZonedDateTime) { %>sameInstant(<% } else + if (field.fieldTypeBigDecimal) { %>sameNumber(<% } %><%= 'DEFAULT_' + field.fieldNameUnderscored.toUpperCase() %><% + if (field.fieldTypeBinary && !field.blobContentTypeText) { %><% + if (databaseTypeCassandra) { %>.array()<% } %>)<% } else + if (field.fieldTypeInteger) { %><% } else + if (field.fieldTypeLong) { %>.intValue()<% } else + if (field.fieldTypeFloat || field.fieldTypeDouble) { %>.doubleValue()<% } else + if (field.fieldTypeBigDecimal) { %>)<% } else + if (field.fieldTypeBoolean) { %>.booleanValue()<% } else + if (field.fieldTypeZonedDateTime) { %>)<% } else + if (!field.fieldTypeString) { %>.toString()<% } %>))<%= !reactive ? ')' : '' %><%_ } _%>; + } + <%_ allowedGetRoles.forEach(secRole => { _%> + + <%_ if (searchEngineCouchbase) { _%> + @Timeout(value = 15, unit = TimeUnit.MINUTES) + <%_ } _%> + <%= getTestPrefix%><%= transactionalAnnotation %> + @WithMockUser(username = "user", authorities = {"<%= secRole %>"}) + void search<%= entityClass %>_with_<%= secRole %>() throws Exception { + search<%= entityClass %>IsAllowed(); + } + <%_ }); _%> + + void search<%= entityClass %>IsForbidden() <% if (!reactive) { %>throws Exception <% } %>{ + // Initialize the database + <%_ if (!primaryKey.derived) { _%> + <%_ for (field of primaryKey.fields.filter(f => !f.autoGenerateByRepository)) { _%> + <%= persistInstance %>.set<%= field.fieldNameCapitalized %>(<%- this.getJavaValueGeneratorForType(field.fieldType) %>); + <%_ } _%> + <%_ } _%> + <%= persistInstance %> = <%= entityInstance %>Repository.<%= saveMethod %>(<%= persistInstance %>)<%= callBlock %>; + <%_ if (searchEngineElasticsearch) { _%> + <%= entityInstance %>SearchRepository.save(<%= persistInstance %>)<%= callBlock %>; + <%_ } else if (searchEngineCouchbase) { _%> + // Wait for the <%= entityInstance %> to be indexed + TestUtil.retryUntilNotEmpty(() -> <%= entityInstance %>Repository.search("id:" + <%= entityInstance %>.get<%= primaryKey.nameCapitalized %>())<% if (reactive) { %>.collectList().block()<% } %>); + <%_ } _%> + + // Search the <%= entityInstance %> + <%_ if (reactive) { _%> + webTestClient.get().uri(ENTITY_SEARCH_API_URL + "?query=id:" + <%= persistInstance %>.get<%= primaryKey.nameCapitalized %>()) + .exchange() + .expectStatus().isForbidden(); + <%_ } else { _%> + rest<%= entityClass %>MockMvc.perform(get(ENTITY_SEARCH_API_URL + "?query=id:" + <%= persistInstance %>.get<%= primaryKey.nameCapitalized %>())) + .andExpect(status().isForbidden()); + <%_ } _%> + } + // #end search<%= entityClass %>IsForbidden + <%_ security.forbiddenRoles.get.forEach(secRole => { _%> + + <%_ if (searchEngineCouchbase) { _%> + @Timeout(value = 15, unit = TimeUnit.MINUTES) + <%_ } _%> + <%= getTestPrefix%><%= transactionalAnnotation %> + @WithMockUser(username = "user", authorities = {"<%= secRole %>"}) + void search<%= entityClass %>_with_<%= secRole %>() throws Exception { + search<%= entityClass %>IsForbidden(); + } + <%_ }); _%> + +<%_ } _%> +} diff --git a/jdl/__test-files__/security1.jdl b/jdl/__test-files__/security1.jdl new file mode 100644 index 000000000000..f222d6b3be7d --- /dev/null +++ b/jdl/__test-files__/security1.jdl @@ -0,0 +1,6 @@ +entity A +entity B +entity C +secure all with roles { + ROLE_ADMIN allows (get, post, put, delete) +} diff --git a/jdl/__test-files__/security2.jdl b/jdl/__test-files__/security2.jdl new file mode 100644 index 000000000000..47d758f7127a --- /dev/null +++ b/jdl/__test-files__/security2.jdl @@ -0,0 +1,6 @@ +entity A +entity B +entity C +secure all except B with roles { + ROLE_ADMIN allows (get, post, put, delete) +} diff --git a/jdl/__test-files__/security3.jdl b/jdl/__test-files__/security3.jdl new file mode 100644 index 000000000000..e48268cd20dc --- /dev/null +++ b/jdl/__test-files__/security3.jdl @@ -0,0 +1,7 @@ +entity A +entity B +entity C + +secure A,B,C with roles { + ROLE_ADMIN allows (get, post, put, delete) +} diff --git a/jdl/converters/jdl-to-json/jdl-to-json-secure-converter.ts b/jdl/converters/jdl-to-json/jdl-to-json-secure-converter.ts new file mode 100644 index 000000000000..ba54565c495e --- /dev/null +++ b/jdl/converters/jdl-to-json/jdl-to-json-secure-converter.ts @@ -0,0 +1,60 @@ +/** + * Copyright 2013-2023 the original author or authors from the JHipster project. + * + * This file is part of the JHipster project, see https://www.jhipster.tech/ + * for more information. + * + * Licensed 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 + * + * https://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 { IJSONSecure, JSONSecure } from '../../jhipster/json-secure.js'; +import { JDLEntity } from '../../models/index.mjs'; +import { JDLSecurityType } from '../../models/jdl-security-type.js'; + +export default { + convert, +}; + +/** + * Converts secure clause to JSON content, + * @param jdlEntities - the JDL object containing entities with secure property. + * @returns {Map} a map having for keys an entity's name and for values its JSON secure clause. + */ +function convert(jdlEntities: JDLEntity[]): Map { + if (!jdlEntities) { + throw new Error('A JDL entities must be passed to convert JDL security to JSON.'); + } + const convertedSecureClauses = new Map(); + + jdlEntities.forEach(jdlEntity => { + const convertedSecure = getConvertedSecureForEntity(jdlEntity); + convertedSecureClauses.set(jdlEntity.name, convertedSecure); + }); + + return convertedSecureClauses; +} + +/** + * Converts a JDLEntity object to an IJSONSecure object with security configurations. + * + * @param {JDLEntity} jdlEntity - The JDLEntity object to be converted. + * @returns {IJSONSecure} - The converted IJSONSecure object with security configurations. + */ +function getConvertedSecureForEntity(jdlEntity: JDLEntity): IJSONSecure { + const jsonSecure: JSONSecure = new JSONSecure({ securityType: JDLSecurityType.None }); + + if (jdlEntity.secure) { + jsonSecure.addConfigFromJDLSecure(jdlEntity.secure); + } + + return jsonSecure; +} diff --git a/jdl/converters/jdl-to-json/jdl-with-applications-to-json-converter.spec.ts b/jdl/converters/jdl-to-json/jdl-with-applications-to-json-converter.spec.ts index e394bf504617..5b25a6ac0064 100644 --- a/jdl/converters/jdl-to-json/jdl-with-applications-to-json-converter.spec.ts +++ b/jdl/converters/jdl-to-json/jdl-with-applications-to-json-converter.spec.ts @@ -46,6 +46,7 @@ import JDLBinaryOption from '../../models/jdl-binary-option.js'; import logger from '../../utils/objects/logger.js'; import { convert } from './jdl-with-applications-to-json-converter.js'; +import { JDLSecurityType, RoleActionType } from '../../models/jdl-security-type.js'; const { Validations: { REQUIRED, UNIQUE, MIN, MAX, MINLENGTH, MAXLENGTH, PATTERN, MINBYTES, MAXBYTES }, @@ -106,7 +107,7 @@ describe('jdl - JDLWithApplicationsToJSONConverter', () => { }); }); - it('should return a map with no entiy', () => { + it('should return a map with no entity', () => { result.forEach(entities => { expect(entities.length).to.equal(0); }); @@ -1908,5 +1909,36 @@ describe('jdl - JDLWithApplicationsToJSONConverter', () => { }); }); }); + context('with entity security options', () => { + let convertedEntity; + + before(() => { + const jdlObject = new JDLObject(); + const application = createJDLApplication({ applicationType: MONOLITH, baseName: 'toto' }); + const entityA = new JDLEntity({ + name: 'A', + tableName: 'entity_a', + comment: 'The best entity', + secure: { + securityType: JDLSecurityType.Roles, + roles: [{ role: 'Test', actionList: [RoleActionType.Post, RoleActionType.Get] }], + }, + }); + application.addEntityName('A'); + jdlObject.addApplication(application); + jdlObject.addEntity(entityA); + const returnedMap: any = convert({ + jdlObject, + }); + convertedEntity = returnedMap.get('toto')[0]; + }); + + it('should convert them', () => { + expect(convertedEntity.secure).to.deep.equal({ + securityType: JDLSecurityType.Roles, + roles: [{ role: 'Test', actionList: [RoleActionType.Post, RoleActionType.Get] }], + }); + }); + }); }); }); diff --git a/jdl/converters/jdl-to-json/jdl-with-applications-to-json-converter.ts b/jdl/converters/jdl-to-json/jdl-with-applications-to-json-converter.ts index de29e145a1ec..5786ba986afd 100644 --- a/jdl/converters/jdl-to-json/jdl-with-applications-to-json-converter.ts +++ b/jdl/converters/jdl-to-json/jdl-with-applications-to-json-converter.ts @@ -21,6 +21,8 @@ import BasicEntityConverter from './jdl-to-json-basic-entity-converter.js'; import FieldConverter from './jdl-to-json-field-converter.js'; import RelationshipConverter from './jdl-to-json-relationship-converter.js'; import OptionConverter from './jdl-to-json-option-converter.js'; +import SecureClauseConverter from './jdl-to-json-secure-converter.js'; +import { JDLSecurityType } from '../../models/jdl-security-type.js'; let entities; let jdlObject; @@ -47,6 +49,7 @@ export function convert(args: any = {}) { setBasicEntityInformation(); setFields(); setRelationships(); + setSecureClause(); setApplicationToEntities(); const entitiesForEachApplication = getEntitiesForEachApplicationMap(); setOptions(entitiesForEachApplication); @@ -94,6 +97,15 @@ function setFields() { }); } +function setSecureClause() { + const convertedSecure = SecureClauseConverter.convert(jdlObject.getEntities()); + convertedSecure.forEach((entitySecure, entityName) => { + if (entitySecure && entitySecure.securityType !== JDLSecurityType.None) { + entities[entityName].secure = entitySecure; + } + }); +} + function setRelationships() { const convertedRelationships = RelationshipConverter.convert(jdlObject.getRelationships(), jdlObject.getEntityNames()); convertedRelationships.forEach((entityRelationships, entityName) => { diff --git a/jdl/converters/jdl-to-json/jdl-without-application-to-json-converter.spec.ts b/jdl/converters/jdl-to-json/jdl-without-application-to-json-converter.spec.ts index eec73f2bb568..77b1c22f995a 100644 --- a/jdl/converters/jdl-to-json/jdl-without-application-to-json-converter.spec.ts +++ b/jdl/converters/jdl-to-json/jdl-without-application-to-json-converter.spec.ts @@ -46,6 +46,7 @@ import JDLRelationship from '../../models/jdl-relationship.js'; import JDLUnaryOption from '../../models/jdl-unary-option.js'; import JDLBinaryOption from '../../models/jdl-binary-option.js'; import logger from '../../utils/objects/logger.js'; +import { JDLSecurityType, RoleActionType } from '../../models/jdl-security-type.js'; const { Validations: { REQUIRED, UNIQUE, MIN, MAX, MINLENGTH, MAXLENGTH, PATTERN, MINBYTES, MAXBYTES }, @@ -1600,5 +1601,36 @@ describe('jdl - JDLWithoutApplicationToJSONConverter', () => { }); }); }); + context('with entity security options', () => { + let convertedEntity; + + before(() => { + const jdlObject = new JDLObject(); + const entityA = new JDLEntity({ + name: 'A', + tableName: 'entity_a', + comment: 'The best entity', + secure: { + securityType: JDLSecurityType.Roles, + roles: [{ role: 'Test', actionList: [RoleActionType.Post, RoleActionType.Get] }], + }, + }); + jdlObject.addEntity(entityA); + const returnedMap: any = convert({ + jdlObject, + applicationName: 'toto', + applicationType: MONOLITH, + databaseType: SQL, + }); + convertedEntity = returnedMap.get('toto')[0]; + }); + + it('should convert them', () => { + expect(convertedEntity.secure).to.deep.equal({ + securityType: JDLSecurityType.Roles, + roles: [{ role: 'Test', actionList: [RoleActionType.Post, RoleActionType.Get] }], + }); + }); + }); }); }); diff --git a/jdl/converters/jdl-to-json/jdl-without-application-to-json-converter.ts b/jdl/converters/jdl-to-json/jdl-without-application-to-json-converter.ts index 68c01aa2f17c..4b8a432ad8e1 100644 --- a/jdl/converters/jdl-to-json/jdl-without-application-to-json-converter.ts +++ b/jdl/converters/jdl-to-json/jdl-without-application-to-json-converter.ts @@ -22,6 +22,8 @@ import FieldConverter from './jdl-to-json-field-converter.js'; import RelationshipConverter from './jdl-to-json-relationship-converter.js'; import OptionConverter from './jdl-to-json-option-converter.js'; import JDLObject from '../../models/jdl-object.js'; +import SecureClauseConverter from './jdl-to-json-secure-converter.js'; +import { JDLSecurityType } from '../../models/jdl-security-type.js'; let entities; let jdlObject: JDLObject | null; @@ -48,6 +50,7 @@ export function convert(args: any = {}) { setOptions(); setFields(); setRelationships(); + setSecureClause(); setApplicationToEntities(); return new Map([[args.applicationName, Object.values(entities)]]); } @@ -86,6 +89,17 @@ function setFields() { }); } +function setSecureClause() { + if (jdlObject) { + const convertedSecure = SecureClauseConverter.convert(jdlObject.getEntities()); + convertedSecure.forEach((entitySecure, entityName) => { + if (entitySecure && entitySecure.securityType !== JDLSecurityType.None) { + entities[entityName].secure = entitySecure; + } + }); + } +} + function setRelationships() { const convertedRelationships = RelationshipConverter.convert(jdlObject!.getRelationships(), jdlObject!.getEntityNames()); convertedRelationships.forEach((entityRelationships, entityName) => { diff --git a/jdl/converters/parsed-jdl-to-jdl-object/__snapshots__/option-converter.spec.ts.snap b/jdl/converters/parsed-jdl-to-jdl-object/__snapshots__/option-converter.spec.ts.snap index 5895fa65f348..a0a3459c609c 100644 --- a/jdl/converters/parsed-jdl-to-jdl-object/__snapshots__/option-converter.spec.ts.snap +++ b/jdl/converters/parsed-jdl-to-jdl-object/__snapshots__/option-converter.spec.ts.snap @@ -146,6 +146,20 @@ exports[`jdl - OptionConverter convertOptions when passing options such as searc ] `; +exports[`jdl - OptionConverter convertOptions when passing options such as secure should convert it 1`] = ` +[ + JDLUnaryOption { + "entityNames": Set { + "A", + }, + "excludedNames": Set { + "B", + }, + "name": "secure", + }, +] +`; + exports[`jdl - OptionConverter convertOptions when passing options such as service should convert it 1`] = ` [ JDLBinaryOption { diff --git a/jdl/converters/parsed-jdl-to-jdl-object/entity-converter.spec.ts b/jdl/converters/parsed-jdl-to-jdl-object/entity-converter.spec.ts index c515cef57cb4..ed7f30d280d6 100644 --- a/jdl/converters/parsed-jdl-to-jdl-object/entity-converter.spec.ts +++ b/jdl/converters/parsed-jdl-to-jdl-object/entity-converter.spec.ts @@ -63,6 +63,87 @@ describe('jdl - EntityConverter', () => { "tableName": "b_table", }, ] +`); + }); + }); + + context('when passing entities with security', () => { + let convertedEntities; + + before(() => { + convertedEntities = convertEntities( + [ + { + name: 'A', + javadoc: '/** No comment */', + secure: { + securityType: 'roles', + roles: [ + { + role: 'ROLE_ADMIN', + actionList: ['GET', 'POST'], + }, + ], + }, + }, + { + name: 'B', + tableName: 'b_table', + secure: { + securityType: 'roles', + roles: [ + { + role: 'ROLE_ADMIN', + actionList: ['GET', 'POST'], + }, + ], + }, + }, + ], + () => [], + ); + }); + + it('should convert them', () => { + expect(convertedEntities).toMatchInlineSnapshot(` +[ + JDLEntity { + "comment": "/** No comment */", + "fields": {}, + "name": "A", + "secure": JDLSecure { + "roles": [ + { + "actionList": [ + "GET", + "POST", + ], + "role": "ROLE_ADMIN", + }, + ], + "securityType": "roles", + }, + "tableName": "A", + }, + JDLEntity { + "comment": undefined, + "fields": {}, + "name": "B", + "secure": JDLSecure { + "roles": [ + { + "actionList": [ + "GET", + "POST", + ], + "role": "ROLE_ADMIN", + }, + ], + "securityType": "roles", + }, + "tableName": "b_table", + }, +] `); }); }); diff --git a/jdl/converters/parsed-jdl-to-jdl-object/entity-converter.ts b/jdl/converters/parsed-jdl-to-jdl-object/entity-converter.ts index e9cd7730c1c5..3dd479157f48 100644 --- a/jdl/converters/parsed-jdl-to-jdl-object/entity-converter.ts +++ b/jdl/converters/parsed-jdl-to-jdl-object/entity-converter.ts @@ -17,8 +17,9 @@ * limitations under the License. */ -import { JDLEntity } from '../../models/index.mjs'; +import { JDLEntity, JDLSecure } from '../../models/index.mjs'; import { formatComment } from '../../utils/format-utils.js'; +import { JDLSecurityType } from '../../models/jdl-security-type.js'; export default { convertEntities }; @@ -40,6 +41,12 @@ export function convertEntities(parsedEntities, jdlFieldGetterFunction): JDLEnti }); const jdlFields = jdlFieldGetterFunction.call(undefined, parsedEntity); jdlEntity.addFields(jdlFields); + if (parsedEntity.secure && parsedEntity.secure.securityType !== JDLSecurityType.None) { + // jdlEntity.secure = jdlSecureGetterFunction.call(undefined, parsedEntity); + jdlEntity.secure = new JDLSecure(parsedEntity.secure); + } else { + delete jdlEntity.secure; + } return jdlEntity; }); } diff --git a/jdl/converters/parsed-jdl-to-jdl-object/parsed-jdl-to-jdl-object-converter.spec.ts b/jdl/converters/parsed-jdl-to-jdl-object/parsed-jdl-to-jdl-object-converter.spec.ts index f543705f94fd..da756191857b 100644 --- a/jdl/converters/parsed-jdl-to-jdl-object/parsed-jdl-to-jdl-object-converter.spec.ts +++ b/jdl/converters/parsed-jdl-to-jdl-object/parsed-jdl-to-jdl-object-converter.spec.ts @@ -1008,6 +1008,61 @@ skipClient D }); }); }); + context('when parsing a JDL with secure entity definition', () => { + context('secure all entities', () => { + let jdlObject; + + before(() => { + const input = JDLReader.parseFromFiles([path.join(__dirname, '..', '..', '__test-files__', 'security1.jdl')]); + jdlObject = ParsedJDLToJDLObjectConverter.parseFromConfigurationObject({ + parsedContent: input, + applicationType: MONOLITH, + }); + }); + + it('should set it', () => { + expect(jdlObject.entities.A.secure.securityType).to.equal('roles'); + expect(jdlObject.entities.B.secure.securityType).to.equal('roles'); + expect(jdlObject.entities.C.secure.securityType).to.equal('roles'); + }); + }); + + context('secure all except C entities', () => { + let jdlObject; + + before(() => { + const input = JDLReader.parseFromFiles([path.join(__dirname, '..', '..', '__test-files__', 'security2.jdl')]); + jdlObject = ParsedJDLToJDLObjectConverter.parseFromConfigurationObject({ + parsedContent: input, + applicationType: MONOLITH, + }); + }); + + it('should set it', () => { + expect(jdlObject.entities.A.secure.securityType).to.equal('roles'); + expect(jdlObject.entities.C.secure.securityType).to.equal('roles'); + expect(jdlObject.entities.B.secure).to.be.undefined; + }); + }); + + context('secure list of entities', () => { + let jdlObject; + + before(() => { + const input = JDLReader.parseFromFiles([path.join(__dirname, '..', '..', '__test-files__', 'security3.jdl')]); + jdlObject = ParsedJDLToJDLObjectConverter.parseFromConfigurationObject({ + parsedContent: input, + applicationType: MONOLITH, + }); + }); + + it('should set it', () => { + expect(jdlObject.entities.A.secure.securityType).to.equal('roles'); + expect(jdlObject.entities.B.secure.securityType).to.equal('roles'); + expect(jdlObject.entities.C.secure.securityType).to.equal('roles'); + }); + }); + }); }); }); }); diff --git a/jdl/converters/parsed-jdl-to-jdl-object/parsed-jdl-to-jdl-object-converter.ts b/jdl/converters/parsed-jdl-to-jdl-object/parsed-jdl-to-jdl-object-converter.ts index c4e647d8ed4e..eee3a2c127b1 100644 --- a/jdl/converters/parsed-jdl-to-jdl-object/parsed-jdl-to-jdl-object-converter.ts +++ b/jdl/converters/parsed-jdl-to-jdl-object/parsed-jdl-to-jdl-object-converter.ts @@ -30,6 +30,7 @@ import { convertValidations } from './validation-converter.js'; import { convertOptions } from './option-converter.js'; import { convertRelationships } from './relationship-converter.js'; import { convertDeployments } from './deployment-converter.js'; +import { JDLEntity, JDLSecure } from '../../models/index.mjs'; let parsedContent; let configuration; @@ -59,6 +60,7 @@ export function parseFromConfigurationObject(configurationObject): JDLObject { fillClassesAndFields(); fillAssociations(); fillOptions(); + fillSecurity(); return jdlObject; } @@ -121,6 +123,10 @@ function getJDLFieldsFromParsedEntity(entity) { return fields; } +// function getJDLSecureFromParsedEntity(entity) { +// return convertSecure(entity.secure); +// } + function addOptionsFromEntityAnnotations() { parsedContent.entities.forEach(entity => { const entityName = entity.name; @@ -198,6 +204,31 @@ function fillOptions() { fillUnaryAndBinaryOptions(); } +function fillSecurity() { + const jdlSecureStmts = parsedContent.secure; + + for (let i = 0; i < jdlSecureStmts.length; i++) { + const jdlSecureStmt = jdlSecureStmts[i]; + // check if entityNames[0] === '*' + if (jdlSecureStmt.entityNames && jdlSecureStmt.entityNames.length === 1 && jdlSecureStmt.entityNames[0] === '*') { + jdlSecureStmt.entityNames = []; + parsedContent.entities.forEach(entity => { + if (!jdlSecureStmt.excludedNames.includes(entity.name)) { + jdlSecureStmt.entityNames.push(entity.name); + } + }); + } + + for (let j = 0; j < jdlSecureStmt.entityNames.length; j++) { + const entityName = jdlSecureStmt.entityNames[j]; + const entity: JDLEntity = jdlObject.getEntity(entityName); + if (entity) { + entity.secure = new JDLSecure(jdlSecureStmt); + } + } + } +} + // TODO: move it to another file? it may not be the parser's responsibility to do it function globallyAddMicroserviceOption(applicationName) { jdlObject.addOption( diff --git a/jdl/jdl-importer.spec.ts b/jdl/jdl-importer.spec.ts index 5408de2b474a..b788a680d016 100644 --- a/jdl/jdl-importer.spec.ts +++ b/jdl/jdl-importer.spec.ts @@ -673,5 +673,103 @@ relationship OneToOne { jestExpect(importState.exportedApplications[0]['generator-jhipster'].clientFramework).toBe(NO_CLIENT_FRAMEWORK); }); }); + context('when parsing entity with a secure definition', () => { + it('import entity with role security definition', () => { + const importState = createImporterFromContent( + ` +entity A + +secure A with roles { + ROLE_A allows (GET, Put, Post, delete) + ROLE_B allows (get) +} +`, + { applicationName: 'MyApp', databaseType: databaseTypes.SQL }, + ).import(); + jestExpect(importState.exportedEntities[0].secure.securityType).toBe('roles'); + jestExpect(importState.exportedEntities[0].secure.roles.length).toBe(2); + jestExpect(importState.exportedEntities[0].secure.roles[0].role).toBe('ROLE_A'); + jestExpect(importState.exportedEntities[0].secure.roles[0].actionList.length).toBe(4); + jestExpect(importState.exportedEntities[0].secure.roles[1].role).toBe('ROLE_B'); + jestExpect(importState.exportedEntities[0].secure.roles[1].actionList.length).toBe(1); + jestExpect(importState.exportedEntities[0].secure.roles[1].actionList[0]).toBe('GET'); + }); + + it('import entity with organizational security definition', () => { + const importState = createImporterFromContent( + ` +entity A + +secure A with organizationalSecurity { + resourceName RESNAME +} +`, + { applicationName: 'MyApp', databaseType: databaseTypes.SQL }, + ).import(); + jestExpect(importState.exportedEntities[0].secure.securityType).toBe('organizationalSecurity'); + jestExpect(importState.exportedEntities[0].secure.organizationalSecurity.resource).toBe('RESNAME'); + }); + + it('import entity with parent privilege security definition', () => { + const importState = createImporterFromContent( + ` +entity A +entity B +relationship ManyToOne { B {a required} to A} + +secure B with parentPrivileges { + parent A + field a +} +`, + { applicationName: 'MyApp', databaseType: databaseTypes.SQL }, + ).import(); + jestExpect(importState.exportedEntities[1].secure.securityType).toBe('parentPrivileges'); + jestExpect(importState.exportedEntities[1].secure.parentPrivileges.parent).toBe('A'); + jestExpect(importState.exportedEntities[1].secure.parentPrivileges.field).toBe('a'); + }); + + it('import entity with relation privileges security definition', () => { + const importState = createImporterFromContent( + ` +entity A +entity B +relationship ManyToOne { B {a required} to A} +relationship ManyToOne { B {c required} to C} +entity C +secure B with relPrivileges { + fromEntity A field a + toEntity C field c +} +`, + { applicationName: 'MyApp', databaseType: databaseTypes.SQL }, + ).import(); + jestExpect(importState.exportedEntities[1].secure.securityType).toBe('relPrivileges'); + jestExpect(importState.exportedEntities[1].secure.relPrivileges.fromEntity).toBe('A'); + jestExpect(importState.exportedEntities[1].secure.relPrivileges.fromField).toBe('a'); + jestExpect(importState.exportedEntities[1].secure.relPrivileges.toEntity).toBe('C'); + jestExpect(importState.exportedEntities[1].secure.relPrivileges.toField).toBe('c'); + }); + + it('import entity with privileges security definition', () => { + const importState = createImporterFromContent( + ` +entity A + +secure A with privileges { + Read requirePrivs (A_ALL_R, A_OWN_R, A_ORG_R, A_USR_R) + Write requirePrivs (A_ALL_W) +} +`, + { applicationName: 'MyApp', databaseType: databaseTypes.SQL }, + ).import(); + jestExpect(importState.exportedEntities[0].secure.securityType).toBe('privileges'); + jestExpect(importState.exportedEntities[0].secure.privileges.length).toBe(2); + jestExpect(importState.exportedEntities[0].secure.privileges[0].action).toBe('read'); + jestExpect(importState.exportedEntities[0].secure.privileges[0].privList.length).toBe(4); + jestExpect(importState.exportedEntities[0].secure.privileges[1].action).toBe('write'); + jestExpect(importState.exportedEntities[0].secure.privileges[1].privList.length).toBe(1); + }); + }); }); }); diff --git a/jdl/jhipster/index.mts b/jdl/jhipster/index.mts index 01f3a83c6226..5b2bf9f4e9d5 100644 --- a/jdl/jhipster/index.mts +++ b/jdl/jhipster/index.mts @@ -29,6 +29,7 @@ export { default as validations } from './validations.js'; export * from './validations.js'; export { default as websocketTypes } from './websocket-types.js'; export { default as checkAndReturnRelationshipOnValue } from './relationship-on-handler-options.js'; +export { default as jsonSecure } from './json-secure.js'; export { IngressTypes as ingressTypes } from './kubernetes-platform-types.js'; export { defaultApplicationOptions }; diff --git a/jdl/jhipster/json-entity.spec.ts b/jdl/jhipster/json-entity.spec.ts index c4b3b2be1530..74b8f9b73cd9 100644 --- a/jdl/jhipster/json-entity.spec.ts +++ b/jdl/jhipster/json-entity.spec.ts @@ -21,6 +21,7 @@ import { jestExpect } from 'esmocha'; import { expect } from 'chai'; import { jsonEntity as JSONEntity } from '../jhipster/index.mjs'; +import { JDLSecurityType, PrivilegeActionType, RoleActionType } from '../models/jdl-security-type.js'; describe('jdl - JSONEntity', () => { describe('new', () => { @@ -121,6 +122,36 @@ JSONEntity { "skipClient": true, "skipServer": true, } +`); + }); + + it('should use them with secure option', () => { + jestExpect(entity).toMatchInlineSnapshot(` +JSONEntity { + "angularJSSuffix": "yes", + "applications": [], + "clientRootFolder": "oh", + "dto": "mapstruct", + "embedded": true, + "entityTableName": "titi", + "fields": [ + 42, + ], + "fluentMethods": true, + "javadoc": "", + "jpaMetamodelFiltering": true, + "microserviceName": "nope", + "name": "Titi", + "pagination": "pagination", + "readOnly": true, + "relationships": [ + 42, + 43, + ], + "service": "serviceClass", + "skipClient": true, + "skipServer": true, +} `); }); }); @@ -291,4 +322,86 @@ JSONEntity { }); }); }); + describe('setSecure', () => { + context('when adding secure on entity', () => { + let entity; + + before(() => { + entity = new JSONEntity({ + entityName: 'seco', + }); + }); + + it('should add none securityType', () => { + entity.setSecure({ securityType: JDLSecurityType.None }); + expect(entity.secure).to.exist; + expect(entity.secure.securityType).to.equal(JDLSecurityType.None); + }); + + it('should add roles securityType', () => { + const roleDef1 = { + role: 'test_role', + actionList: [RoleActionType.Put, RoleActionType.Post, RoleActionType.Get, RoleActionType.Delete], + }; + + entity.setSecure({ securityType: JDLSecurityType.Roles, roles: [roleDef1], comment: 'comment' }); + expect(entity.secure.securityType).equal(JDLSecurityType.Roles); + expect(entity.secure.comment).equal('comment'); + expect(entity.secure.roles).to.deep.equal([roleDef1]); + }); + + it('should add privileges securityType', () => { + const privDef1 = { + action: PrivilegeActionType.Read, + privList: ['PRIV1_R', 'PRIV2_R'], + }; + + entity.setSecure({ securityType: JDLSecurityType.Privileges, privileges: [privDef1], comment: 'comment' }); + expect(entity.secure.securityType).equal(JDLSecurityType.Privileges); + expect(entity.secure.comment).equal('comment'); + expect(entity.secure.privileges).to.deep.equal([privDef1]); + }); + + it('should add organizationalSecurity securityType', () => { + const organizationalSecurityDef1 = { + resource: 'resourceName', + }; + + entity.setSecure({ + securityType: JDLSecurityType.OrganizationalSecurity, + organizationalSecurity: organizationalSecurityDef1, + comment: 'comment', + }); + expect(entity.secure.securityType).equal(JDLSecurityType.OrganizationalSecurity); + expect(entity.secure.comment).equal('comment'); + expect(entity.secure.organizationalSecurity).to.deep.equal(organizationalSecurityDef1); + }); + + it('should add parent privileges securityType', () => { + const parentPrivilegesDef1 = { + parent: 'Parent', + field: 'parentId', + }; + + entity.setSecure({ securityType: JDLSecurityType.ParentPrivileges, parentPrivileges: parentPrivilegesDef1, comment: 'comment' }); + expect(entity.secure.securityType).equal(JDLSecurityType.ParentPrivileges); + expect(entity.secure.comment).equal('comment'); + expect(entity.secure.parentPrivileges).to.deep.equal(parentPrivilegesDef1); + }); + + it('should add relational privileges securityType', () => { + const relPrivilegesDef1 = { + fromEntity: 'FromEntity', + fromField: 'FromField', + toEntity: 'ToEntity', + toField: 'toField', + }; + + entity.setSecure({ securityType: JDLSecurityType.RelPrivileges, relPrivileges: relPrivilegesDef1, comment: 'comment' }); + expect(entity.secure.securityType).equal(JDLSecurityType.RelPrivileges); + expect(entity.secure.comment).equal('comment'); + expect(entity.secure.relPrivileges).to.deep.equal(relPrivilegesDef1); + }); + }); + }); }); diff --git a/jdl/jhipster/json-entity.ts b/jdl/jhipster/json-entity.ts index 393ccd488ae9..1620e24b9c5d 100644 --- a/jdl/jhipster/json-entity.ts +++ b/jdl/jhipster/json-entity.ts @@ -19,6 +19,8 @@ import { merge } from '../utils/object-utils.js'; import { upperFirst } from '../utils/string-utils.js'; +import JSONSecure from './json-secure.js'; +import { JDLSecurityType } from '../models/jdl-security-type.js'; /** * The JSONEntity class represents a read-to-be exported to JSON entity. @@ -75,6 +77,9 @@ class JSONEntity { if (merged.skipClient) { this.skipClient = merged.skipClient; } + if (args.secure && args.secure.securityType !== JDLSecurityType.None) { + this.secure = new JSONSecure(args.secure); + } this.applications = []; } @@ -109,6 +114,14 @@ class JSONEntity { this[optionName] = options[optionName]; }); } + + setSecure(secure: any) { + if (secure && secure.securityType) { + this.secure = new JSONSecure(secure); + } else { + this.secure = new JSONSecure({ securityType: JDLSecurityType.None }); + } + } } export default JSONEntity; diff --git a/jdl/jhipster/json-secure.spec.ts b/jdl/jhipster/json-secure.spec.ts new file mode 100644 index 000000000000..a2530aa830c4 --- /dev/null +++ b/jdl/jhipster/json-secure.spec.ts @@ -0,0 +1,145 @@ +import { expect } from 'chai'; +import { jsonSecure as JSONSecure } from '../jhipster/index.mjs'; +import { JDLSecurityType, PrivilegeActionType, RoleActionType } from '../models/jdl-security-type.js'; +import { JDLSecure } from '../models/index.mjs'; + +describe('JSONSecure', () => { + it('should initialize with securityType None and no additional properties', () => { + const obj = new JSONSecure({ securityType: JDLSecurityType.None }); + expect(obj.securityType).to.equal(JDLSecurityType.None); + expect(obj.roles).to.be.undefined; + expect(obj.privileges).to.be.undefined; + expect(obj.organizationalSecurity).to.be.undefined; + expect(obj.parentPrivileges).to.be.undefined; + expect(obj.relPrivileges).to.be.undefined; + expect(obj.comment).to.be.undefined; + }); + + it('should initialize with the provided securityType and comment', () => { + const obj = new JSONSecure({ securityType: JDLSecurityType.Roles, comment: 'Test' }); + expect(obj.securityType).to.equal(JDLSecurityType.Roles); + expect(obj.comment).to.equal('Test'); + }); + + it('should initialize roles if securityType is Roles', () => { + const obj = new JSONSecure({ securityType: JDLSecurityType.Roles, roles: [{ role: 'Test', actionList: [] }] }); + expect(obj.roles).to.deep.equal([{ role: 'Test', actionList: [] }]); + }); + + it('should initialize roles if securityType is Roles with actions Get and Post', () => { + const obj = new JSONSecure({ + securityType: JDLSecurityType.Roles, + roles: [{ role: 'Test', actionList: [RoleActionType.Get, RoleActionType.Post] }], + }); + expect(obj.roles).to.deep.equal([{ role: 'Test', actionList: [RoleActionType.Get, RoleActionType.Post] }]); + }); + + it('should initialize organizationalSecurity if securityType is OrganizationalSecurity', () => { + const obj = new JSONSecure({ + securityType: JDLSecurityType.OrganizationalSecurity, + organizationalSecurity: { resource: 'TestResource' }, + }); + expect(obj.securityType).to.equal(JDLSecurityType.OrganizationalSecurity); + expect(obj.organizationalSecurity).to.deep.equal({ resource: 'TestResource' }); + }); + + it('should initialize parentPrivileges if securityType is ParentPrivileges', () => { + const obj = new JSONSecure({ + securityType: JDLSecurityType.ParentPrivileges, + parentPrivileges: { parent: 'TestParent', field: 'TestField' }, + }); + expect(obj.securityType).to.equal(JDLSecurityType.ParentPrivileges); + expect(obj.parentPrivileges).to.deep.equal({ parent: 'TestParent', field: 'TestField' }); + }); + + it('should initialize privileges if securityType is Privileges', () => { + const obj = new JSONSecure({ + securityType: JDLSecurityType.Privileges, + privileges: [{ action: PrivilegeActionType.Read, privList: ['Admin'] }], + }); + expect(obj.securityType).to.equal(JDLSecurityType.Privileges); + expect(obj.privileges).to.deep.equal([{ action: PrivilegeActionType.Read, privList: ['Admin'] }]); + }); + + it('should initialize relational privileges securityType', () => { + const relPrivilegesDef1 = { + fromEntity: 'FromEntity', + fromField: 'FromField', + toEntity: 'ToEntity', + toField: 'toField', + }; + + const obj = new JSONSecure({ + securityType: JDLSecurityType.RelPrivileges, + relPrivileges: relPrivilegesDef1, + comment: 'comment', + }); + + expect(obj.securityType).equal(JDLSecurityType.RelPrivileges); + expect(obj.comment).equal('comment'); + expect(obj.relPrivileges).to.deep.equal(relPrivilegesDef1); + }); + + it('should initialize roles from JDLSecure if securityType is Roles with actions Get and Post', () => { + const obj = new JSONSecure({ securityType: JDLSecurityType.None }); + const jdl = new JDLSecure({ + securityType: JDLSecurityType.Roles, + roles: [{ role: 'Test', actionList: [RoleActionType.Get, RoleActionType.Post] }], + }); + obj.addConfigFromJDLSecure(jdl); + expect(obj.roles).to.deep.equal([{ role: 'Test', actionList: [RoleActionType.Get, RoleActionType.Post] }]); + }); + + it('should initialize organizationalSecurity from JDLSecure if securityType is OrganizationalSecurity', () => { + const obj = new JSONSecure({ securityType: JDLSecurityType.None }); + const jdl = new JDLSecure({ + securityType: JDLSecurityType.OrganizationalSecurity, + organizationalSecurity: { resource: 'TestResource' }, + }); + obj.addConfigFromJDLSecure(jdl); + expect(obj.securityType).to.equal(JDLSecurityType.OrganizationalSecurity); + expect(obj.organizationalSecurity).to.deep.equal({ resource: 'TestResource' }); + }); + + it('should initialize parentPrivileges if securityType is ParentPrivileges', () => { + const obj = new JSONSecure({ securityType: JDLSecurityType.None }); + const jdl = new JDLSecure({ + securityType: JDLSecurityType.ParentPrivileges, + parentPrivileges: { parent: 'TestParent', field: 'TestField' }, + }); + obj.addConfigFromJDLSecure(jdl); + expect(obj.securityType).to.equal(JDLSecurityType.ParentPrivileges); + expect(obj.parentPrivileges).to.deep.equal({ parent: 'TestParent', field: 'TestField' }); + }); + + it('should initialize privileges if securityType is Privileges', () => { + const obj = new JSONSecure({ securityType: JDLSecurityType.None }); + const jdl = new JDLSecure({ + securityType: JDLSecurityType.Privileges, + privileges: [{ action: PrivilegeActionType.Read, privList: ['Admin'] }], + }); + obj.addConfigFromJDLSecure(jdl); + expect(obj.securityType).to.equal(JDLSecurityType.Privileges); + expect(obj.privileges).to.deep.equal([{ action: PrivilegeActionType.Read, privList: ['Admin'] }]); + }); + + it('should initialize relational privileges securityType', () => { + const obj = new JSONSecure({ securityType: JDLSecurityType.None }); + const relPrivilegesDef1 = { + fromEntity: 'FromEntity', + fromField: 'FromField', + toEntity: 'ToEntity', + toField: 'toField', + }; + + const jdl = new JSONSecure({ + securityType: JDLSecurityType.RelPrivileges, + relPrivileges: relPrivilegesDef1, + comment: 'comment', + }); + obj.addConfigFromJDLSecure(jdl); + expect(obj.securityType).equal(JDLSecurityType.RelPrivileges); + expect(obj.comment).equal('comment'); + expect(obj.relPrivileges).to.deep.equal(relPrivilegesDef1); + }); +}); diff --git a/jdl/jhipster/json-secure.ts b/jdl/jhipster/json-secure.ts new file mode 100644 index 000000000000..58b309e518ae --- /dev/null +++ b/jdl/jhipster/json-secure.ts @@ -0,0 +1,132 @@ +import { JDLSecurityType, PrivilegeActionType, RoleActionType } from '../models/jdl-security-type.js'; +import { IJDLSecure } from '../models/jdl-secure.js'; + +interface IJSONRoleSecurity { + role: string; + actionList: RoleActionType[]; +} + +interface IJSONPrivilegeSecurity { + action: PrivilegeActionType; + privList: string[]; +} + +interface IJSONOrganizationalSecurity { + resource: string; +} + +interface IJSONParentPrivileges { + parent: string; + field: string; +} + +interface IJSONRelPrivileges { + fromEntity: string; + fromField: string; + toEntity: string; + toField: string; +} + +export interface IJSONSecure { + securityType: JDLSecurityType; + roles?: IJSONRoleSecurity[]; + privileges?: IJSONPrivilegeSecurity[]; + organizationalSecurity?: IJSONOrganizationalSecurity; + parentPrivileges?: IJSONParentPrivileges; + relPrivileges?: IJSONRelPrivileges; + comment?: string; +} +export class JSONSecure implements IJSONSecure { + securityType: JDLSecurityType = JDLSecurityType.None; + roles?: IJSONRoleSecurity[]; + privileges?: IJSONPrivilegeSecurity[]; + organizationalSecurity?: IJSONOrganizationalSecurity; + parentPrivileges?: IJSONParentPrivileges; + relPrivileges?: IJSONRelPrivileges; + comment?: string; + + /** + * Constructor for creating a secure definition. + * Throws an error if no arguments are provided or if the security type is missing. + * + * @param {IJSONSecure} args - The arguments containing the security type. + * @throws {Error} If no arguments are provided or if the security type is missing. + */ + constructor(args: IJSONSecure) { + if (!args) { + throw new Error('No arguments provided.'); + } + if (!args.securityType) { + throw new Error('The security type is mandatory to create a secure definition.'); + } + this.addConfig(args); + } + + /** + * Update the configuration with the provided JSON Secure object. + * + * @param {IJSONSecure} config - The JSON Secure object containing the configuration to be added. + * @returns {void} + */ + public addConfig(config: IJSONSecure): void { + this.securityType = config.securityType; + this.comment = config.comment; + switch (config.securityType) { + case JDLSecurityType.Roles: + this.roles = config.roles ? [...config.roles] : []; + break; + case JDLSecurityType.Privileges: + this.privileges = config.privileges ? [...config.privileges] : []; + break; + case JDLSecurityType.OrganizationalSecurity: + this.organizationalSecurity = config.organizationalSecurity; + break; + case JDLSecurityType.ParentPrivileges: + this.parentPrivileges = config.parentPrivileges; + break; + case JDLSecurityType.RelPrivileges: + this.relPrivileges = config.relPrivileges; + break; + default: + this.securityType = JDLSecurityType.None; + } + this.removeUndefinedproperties(); + } + + /** + * Adds configuration from a JDL Secure object. + * + * @param {IJDLSecure} config - The JDL Secure object containing the configuration to add. + * @return {void} + */ + public addConfigFromJDLSecure(config: IJDLSecure): void { + this.securityType = config.securityType; + this.comment = config.comment; + switch (config.securityType) { + case JDLSecurityType.Roles: + this.roles = config.roles ? [...config.roles] : []; + break; + case JDLSecurityType.Privileges: + this.privileges = config.privileges ? [...config.privileges] : []; + break; + case JDLSecurityType.OrganizationalSecurity: + this.organizationalSecurity = config.organizationalSecurity; + break; + case JDLSecurityType.ParentPrivileges: + this.parentPrivileges = config.parentPrivileges; + break; + case JDLSecurityType.RelPrivileges: + this.relPrivileges = config.relPrivileges; + break; + default: + this.securityType = JDLSecurityType.None; + } + this.removeUndefinedproperties(); + } + + public removeUndefinedproperties(): void { + Object.keys(this).forEach(key => this[key] === undefined && delete this[key]); + } +} + +export default JSONSecure; diff --git a/jdl/jhipster/reserved-keywords/jhipster.ts b/jdl/jhipster/reserved-keywords/jhipster.ts index 30a28fdffd2c..bf3d994eafb4 100644 --- a/jdl/jhipster/reserved-keywords/jhipster.ts +++ b/jdl/jhipster/reserved-keywords/jhipster.ts @@ -36,4 +36,5 @@ export default [ 'PRINCIPAL', 'ENTITY', 'RESULT', + 'SECURE', ]; diff --git a/jdl/jhipster/unary-options.spec.ts b/jdl/jhipster/unary-options.spec.ts index 42901ab24122..6bb288849f46 100644 --- a/jdl/jhipster/unary-options.spec.ts +++ b/jdl/jhipster/unary-options.spec.ts @@ -58,6 +58,7 @@ describe('jdl - UnaryOptions', () => { "readOnly", "filter", "embedded", + "secure", ] `); }); diff --git a/jdl/jhipster/unary-options.ts b/jdl/jhipster/unary-options.ts index 07ca27882b81..0c83ad7eb0bb 100644 --- a/jdl/jhipster/unary-options.ts +++ b/jdl/jhipster/unary-options.ts @@ -25,6 +25,7 @@ const Options: any = { READ_ONLY: 'readOnly', FILTER: 'filter', EMBEDDED: 'embedded', + SECURE: 'secure', }; const optionNames = Object.values(Options); diff --git a/jdl/models/index.mts b/jdl/models/index.mts index e987edd16be4..ab3a7f71ea98 100644 --- a/jdl/models/index.mts +++ b/jdl/models/index.mts @@ -19,3 +19,4 @@ // eslint-disable-next-line import/prefer-default-export export { default as JDLEntity } from './jdl-entity.js'; export { default as JDLEnum } from './jdl-enum.js'; +export { default as JDLSecure } from './jdl-secure.js'; diff --git a/jdl/models/jdl-entity.ts b/jdl/models/jdl-entity.ts index 592f97f969a6..051f6aff742a 100644 --- a/jdl/models/jdl-entity.ts +++ b/jdl/models/jdl-entity.ts @@ -20,11 +20,14 @@ import { merge } from '../utils/object-utils.js'; import getTableNameFromEntityName from '../jhipster/entity-table-name-creator.js'; import JDLField from './jdl-field.js'; +import { JDLSecure } from './index.mjs'; +import { IJDLSecure } from './jdl-secure.js'; export default class JDLEntity { name: any; tableName: any; fields: Record; + secure?: IJDLSecure; comment: any; constructor(args) { @@ -36,6 +39,11 @@ export default class JDLEntity { this.tableName = merged.tableName || merged.name; this.fields = merged.fields; this.comment = merged.comment; + if (merged.secure) { + this.secure = merged.secure; + } else { + delete this.secure; + } } /** @@ -53,6 +61,10 @@ export default class JDLEntity { this.fields[field.name] = field; } + addSecure(secure: IJDLSecure) { + this.secure = new JDLSecure(secure); + } + forEachField(functionToApply: (field: JDLField, index: number, array: JDLField[]) => void) { if (!functionToApply) { throw new Error('A function must be passed to iterate over fields'); diff --git a/jdl/models/jdl-secure.spec.ts b/jdl/models/jdl-secure.spec.ts new file mode 100644 index 000000000000..71d08dc330dd --- /dev/null +++ b/jdl/models/jdl-secure.spec.ts @@ -0,0 +1,145 @@ +/** + * Copyright 2013-2023 the original author or authors from the JHipster project. + * + * This file is part of the JHipster project, see https://www.jhipster.tech/ + * for more information. + * + * Licensed 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 + * + * https://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 { expect } from 'chai'; +import { JDLSecurityType, PrivilegeActionType, RoleActionType } from './jdl-security-type.js'; +import { JDLSecure } from './index.mjs'; +import { IJDLSecure, IOrganizationalSecurity, IParentPrivileges, IPrivilegeSecurity, IRelPrivileges, IRoleSecurity } from './jdl-secure.js'; + +describe('JDLSecure', () => { + it('should initialize with securityType None and no additional properties', () => { + const obj = new JDLSecure({ securityType: JDLSecurityType.None }); + expect(obj.securityType).to.equal(JDLSecurityType.None); + expect(obj.roles).to.be.undefined; + expect(obj.privileges).to.be.undefined; + expect(obj.organizationalSecurity).to.be.undefined; + expect(obj.parentPrivileges).to.be.undefined; + expect(obj.relPrivileges).to.be.undefined; + expect(obj.comment).to.be.undefined; + }); + + it('should initialize with the provided securityType and comment', () => { + const obj = new JDLSecure({ securityType: JDLSecurityType.Roles, comment: 'Test' }); + obj.addConfig({ securityType: JDLSecurityType.Roles, comment: 'Test' }); + expect(obj.securityType).to.equal(JDLSecurityType.Roles); + expect(obj.comment).to.equal('Test'); + }); + + it('should initialize roles if securityType is Roles', () => { + const obj = new JDLSecure({ securityType: JDLSecurityType.Roles }); + obj.addConfig({ securityType: JDLSecurityType.Roles, roles: [{ role: 'Test', actionList: [] }] }); + expect(obj.roles).to.deep.equal([{ role: 'Test', actionList: [] }]); + }); + + it('should initialize roles if securityType is Roles with actions Get and Post', () => { + const obj = new JDLSecure({ + securityType: JDLSecurityType.Roles, + roles: [{ role: 'Test', actionList: [RoleActionType.Get, RoleActionType.Post] }], + }); + expect(obj.roles).to.deep.equal([{ role: 'Test', actionList: [RoleActionType.Get, RoleActionType.Post] }]); + }); + + it('should initialize organizationalSecurity if securityType is OrganizationalSecurity', () => { + const obj = new JDLSecure({ + securityType: JDLSecurityType.OrganizationalSecurity, + organizationalSecurity: { resource: 'TestResource' }, + }); + expect(obj.securityType).to.equal(JDLSecurityType.OrganizationalSecurity); + expect(obj.organizationalSecurity).to.deep.equal({ resource: 'TestResource' }); + }); + + it('should initialize parentPrivileges if securityType is ParentPrivileges', () => { + const obj = new JDLSecure({ securityType: JDLSecurityType.ParentPrivileges }); + obj.addConfig({ + securityType: JDLSecurityType.ParentPrivileges, + parentPrivileges: { parent: 'TestParent', field: 'TestField' }, + }); + expect(obj.securityType).to.equal(JDLSecurityType.ParentPrivileges); + expect(obj.parentPrivileges).to.deep.equal({ parent: 'TestParent', field: 'TestField' }); + }); + + it('should initialize privileges if securityType is Privileges', () => { + const obj = new JDLSecure({ securityType: JDLSecurityType.Privileges }); + obj.addConfig({ + securityType: JDLSecurityType.Privileges, + privileges: [{ action: PrivilegeActionType.Read, privList: ['Admin'] }], + }); + expect(obj.securityType).to.equal(JDLSecurityType.Privileges); + expect(obj.privileges).to.deep.equal([{ action: PrivilegeActionType.Read, privList: ['Admin'] }]); + }); + + it('should initialize relational privileges securityType', () => { + const relPrivilegesDef1 = { + fromEntity: 'FromEntity', + fromField: 'FromField', + toEntity: 'ToEntity', + toField: 'toField', + }; + + const obj = new JDLSecure({ + securityType: JDLSecurityType.RelPrivileges, + relPrivileges: relPrivilegesDef1, + comment: 'comment', + }); + + expect(obj.securityType).equal(JDLSecurityType.RelPrivileges); + expect(obj.comment).equal('comment'); + expect(obj.relPrivileges).to.deep.equal(relPrivilegesDef1); + }); + + it('should correctly convert a configuration with JDLSecurityType.Roles to string', function () { + const roleSec: IRoleSecurity[] = [{ role: 'ADMIN', actionList: [RoleActionType.Post, RoleActionType.Put] }]; + const config: IJDLSecure = { securityType: JDLSecurityType.Roles, roles: roleSec }; + const jdlSec = new JDLSecure(config); + + expect(jdlSec.toString()).to.equal('roles ADMIN ( POST PUT)'); + }); + + it('should correctly convert a configuration with JDLSecurityType.Privileges to string', function () { + const privSec: IPrivilegeSecurity[] = [{ action: PrivilegeActionType.Read, privList: ['privilege1', 'privilege2'] }]; + const config: IJDLSecure = { securityType: JDLSecurityType.Privileges, privileges: privSec }; + const jdlSec = new JDLSecure(config); + + expect(jdlSec.toString()).to.equal('privileges read ( privilege1 privilege2)'); + }); + + it('should correctly convert a configuration with JDLSecurityType.OrganizationalSecurity to string', function () { + const orgSec: IOrganizationalSecurity = { resource: 'resource1' }; + const config: IJDLSecure = { securityType: JDLSecurityType.OrganizationalSecurity, organizationalSecurity: orgSec }; + const jdlSec = new JDLSecure(config); + + expect(jdlSec.toString()).to.equal('organizationalSecurity resource resource1'); + }); + + it('should correctly convert a configuration with JDLSecurityType.ParentPrivileges to string', function () { + const ppSec: IParentPrivileges = { parent: 'parent1', field: 'field1' }; + const config: IJDLSecure = { securityType: JDLSecurityType.ParentPrivileges, parentPrivileges: ppSec }; + const jdlSec = new JDLSecure(config); + + expect(jdlSec.toString()).to.equal('parentPrivileges parent1 field1'); + }); + + it('should correctly convert a configuration with JDLSecurityType.RelPrivileges to string', function () { + const rpSec: IRelPrivileges = { fromEntity: 'entity1', fromField: 'field1', toEntity: 'entity2', toField: 'field2' }; + const config: IJDLSecure = { securityType: JDLSecurityType.RelPrivileges, relPrivileges: rpSec }; + const jdlSec = new JDLSecure(config); + + expect(jdlSec.toString()).to.equal('relPrivileges entity1 field1 entity2 field2'); + }); +}); diff --git a/jdl/models/jdl-secure.ts b/jdl/models/jdl-secure.ts new file mode 100644 index 000000000000..3714808690c6 --- /dev/null +++ b/jdl/models/jdl-secure.ts @@ -0,0 +1,139 @@ +/** + * Copyright 2013-2023 the original author or authors from the JHipster project. + * + * This file is part of the JHipster project, see https://www.jhipster.tech/ + * for more information. + * + * Licensed 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 + * + * https://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 { JDLSecurityType, PrivilegeActionType, RoleActionType } from './jdl-security-type.js'; + +export interface IRoleSecurity { + role: string; + actionList: RoleActionType[]; +} + +export interface IPrivilegeSecurity { + action: PrivilegeActionType; + privList: string[]; +} + +export interface IOrganizationalSecurity { + resource: string; +} + +export interface IParentPrivileges { + parent: string; + field: string; +} + +export interface IRelPrivileges { + fromEntity: string; + fromField: string; + toEntity: string; + toField: string; +} + +export interface IJDLSecure { + securityType: JDLSecurityType; + roles?: IRoleSecurity[]; + privileges?: IPrivilegeSecurity[]; + organizationalSecurity?: IOrganizationalSecurity; + parentPrivileges?: IParentPrivileges; + relPrivileges?: IRelPrivileges; + comment?: string; +} +export default class JDLSecure implements IJDLSecure { + securityType: JDLSecurityType = JDLSecurityType.None; + roles?: IRoleSecurity[]; + privileges?: IPrivilegeSecurity[]; + organizationalSecurity?: IOrganizationalSecurity; + parentPrivileges?: IParentPrivileges; + relPrivileges?: IRelPrivileges; + comment?: string; + + constructor(args: IJDLSecure) { + if (!args) { + throw new Error('A secure definition must at least contain a security type.'); + } + if (!args.securityType) { + throw new Error('The security type is mandatory to create a secure definition.'); + } + this.addConfig(args); + } + + public addConfig(config: IJDLSecure) { + this.securityType = config.securityType; + this.comment = config.comment; + switch (config.securityType) { + case JDLSecurityType.Roles: + this.roles = config.roles ? [...config.roles] : []; + break; + case JDLSecurityType.Privileges: + this.privileges = config.privileges ? [...config.privileges] : []; + break; + case JDLSecurityType.OrganizationalSecurity: + this.organizationalSecurity = config.organizationalSecurity; + break; + case JDLSecurityType.ParentPrivileges: + this.parentPrivileges = config.parentPrivileges; + break; + case JDLSecurityType.RelPrivileges: + this.relPrivileges = config.relPrivileges; + break; + default: + this.securityType = JDLSecurityType.None; + } + this.removeUndefinedproperties(); + } + + public toString(): string { + let string = ''; + if (this.comment) { + string += `/**\n${this.comment + .split('\n') + .map(line => ` * ${line}\n`) + .join('')} */\n`; + } + string += `${this.securityType}`; + if (this.securityType === JDLSecurityType.Roles) { + this.roles?.forEach(item => { + string += ` ${item.role} (`; + item.actionList.forEach(action => { + string += ` ${action}`; + }); + string += ')'; + }); + } else if (this.securityType === JDLSecurityType.Privileges) { + this.privileges?.forEach(item => { + string += ` ${item.action} (`; + item.privList.forEach(priv => { + string += ` ${priv}`; + }); + string += ')'; + }); + } else if (this.securityType === JDLSecurityType.OrganizationalSecurity && this.organizationalSecurity) { + string += ` resource ${this.organizationalSecurity.resource}`; + } else if (this.securityType === JDLSecurityType.ParentPrivileges && this.parentPrivileges) { + string += ` ${this.parentPrivileges.parent} ${this.parentPrivileges.field}`; + } else if (this.securityType === JDLSecurityType.RelPrivileges && this.relPrivileges) { + string += ` ${this.relPrivileges.fromEntity} ${this.relPrivileges.fromField} ${this.relPrivileges.toEntity} ${this.relPrivileges.toField}`; + } + return string; + } + + public removeUndefinedproperties(): void { + Object.keys(this).forEach(key => this[key] === undefined && delete this[key]); + } +} diff --git a/jdl/models/jdl-security-type.ts b/jdl/models/jdl-security-type.ts new file mode 100644 index 000000000000..a26de5e31320 --- /dev/null +++ b/jdl/models/jdl-security-type.ts @@ -0,0 +1,22 @@ +export enum JDLSecurityType { + None = 'none', + Roles = 'roles', + Privileges = 'privileges', + OrganizationalSecurity = 'organizationalSecurity', + ParentPrivileges = 'parentPrivileges', + RelPrivileges = 'relPrivileges', + CustomSecurity = 'customSecurity', +} + +export enum PrivilegeActionType { + Read = 'read', + Write = 'write', + Execute = 'execute', +} + +export enum RoleActionType { + Put = 'PUT', + Post = 'POST', + Get = 'GET', + Delete = 'DELETE', +} diff --git a/jdl/parsing/dsl-api.spec.ts b/jdl/parsing/dsl-api.spec.ts index 9b7afde9367c..05319217158d 100644 --- a/jdl/parsing/dsl-api.spec.ts +++ b/jdl/parsing/dsl-api.spec.ts @@ -122,7 +122,7 @@ describe('jdl - JDL DSL API', () => { }); it('should provide suggestions', () => { - expect(result).to.have.lengthOf(11); + expect(result).to.have.lengthOf(12); expect(result).to.have.members([ tokens.AT, tokens.APPLICATION, @@ -131,6 +131,7 @@ describe('jdl - JDL DSL API', () => { tokens.ENTITY, tokens.RELATIONSHIP, tokens.ENUM, + tokens.SECURE, tokens.JAVADOC, tokens.UNARY_OPTION, tokens.BINARY_OPTION, diff --git a/jdl/parsing/grammar.spec.ts b/jdl/parsing/grammar.spec.ts index 5ac3cdb5fdee..b68996ea3805 100644 --- a/jdl/parsing/grammar.spec.ts +++ b/jdl/parsing/grammar.spec.ts @@ -2034,4 +2034,34 @@ entity A { }); }); }); + context('when parsing secure option', () => { + context('define entities', () => { + it('secure using the * keyword', () => { + const content = parseFromContent('secure * with roles { ROLE_ADMIN allows (get)}'); + const parsedSecure = content.secure[0]; + expect(parsedSecure.entityNames).to.deep.equal(['*']); + }); + it('secure using the all keyword', () => { + const content = parseFromContent('secure * with roles { ROLE_ADMIN allows (get)}'); + const parsedSecure = content.secure[0]; + expect(parsedSecure.entityNames).to.deep.equal(['*']); + }); + it('secure using several entities', () => { + const content = parseFromContent('secure A,B,C with roles { ROLE_ADMIN allows (get)}'); + const parsedSecure = content.secure[0]; + expect(parsedSecure.entityNames).to.deep.equal(['A', 'B', 'C']); + }); + it('secure using the except keyword', () => { + const content = parseFromContent('secure * except A with roles { ROLE_ADMIN allows (get)}'); + const parsedSecure = content.secure[0]; + expect(parsedSecure.entityNames).to.deep.equal(['*']); + expect(parsedSecure.excludedNames).to.deep.equal(['A']); + }); + it('secure entity with role that is empty', () => { + const content = parseFromContent('secure A with roles { ROLE_ADMIN allows ()}'); + const parsedSecure = content.secure[0]; + expect(parsedSecure.entityNames).to.deep.equal(['A']); + }); + }); + }); }); diff --git a/jdl/parsing/jdl-ast-builder-visitor.ts b/jdl/parsing/jdl-ast-builder-visitor.ts index 36f306f4c9c9..d7e4ca30d37a 100644 --- a/jdl/parsing/jdl-ast-builder-visitor.ts +++ b/jdl/parsing/jdl-ast-builder-visitor.ts @@ -52,6 +52,7 @@ export default class JDLAstBuilderVisitor extends BaseJDLCSTVisitor { enums: [], options: {}, useOptions: [], + secure: [], }; if (context.constantDeclaration) { @@ -80,6 +81,9 @@ export default class JDLAstBuilderVisitor extends BaseJDLCSTVisitor { if (context.enumDeclaration) { ast.enums = context.enumDeclaration.map(this.visit, this); } + if (context.secureDeclaration) { + ast.secure = context.secureDeclaration.map(this.visit, this); + } if (context.unaryOptionDeclaration) { context.unaryOptionDeclaration.map(this.visit, this).forEach(option => { @@ -163,6 +167,150 @@ export default class JDLAstBuilderVisitor extends BaseJDLCSTVisitor { }; } + secureDeclaration(context) { + // extract list of entities + const contextName = 'secureEntityList'; + + if (!context[contextName]) { + return {}; + } + + const { entityNames, excludedNames } = this.visit(context.secureEntityList); + let securityType = ''; + let roles = []; + let privileges = []; + let organizationalSecurity = { resource: null }; + let parentPrivileges = { parent: null, field: null }; + let relPrivileges = { fromEntity: null, fromField: null, toEntity: null, toField: null }; + + if (context.rolesSecurity) { + securityType = 'roles'; + roles = this.visit(context.rolesSecurity); + } + + if (context.privilegesSecurity) { + securityType = 'privileges'; + privileges = this.visit(context.privilegesSecurity); + } + + if (context.organizationalSecurity) { + securityType = 'organizationalSecurity'; + organizationalSecurity = this.visit(context.organizationalSecurity); + } + + if (context.parentPrivilegesSecurity) { + securityType = 'parentPrivileges'; + parentPrivileges = this.visit(context.parentPrivilegesSecurity); + } + + if (context.relPrivilegesSecurity) { + securityType = 'relPrivileges'; + relPrivileges = this.visit(context.relPrivilegesSecurity); + } + + return { + entityNames, + excludedNames, + securityType, + roles, + privileges, + parentPrivileges, + relPrivileges, + organizationalSecurity, + }; + } + + rolesSecurity(context) { + // return _.map(context.roleDefinition, element => this.visit(element)); + return context.roleDefinition.map(element => this.visit(element)); + } + + privilegesSecurity(context) { + // return _.map(context.privDefinition, element => this.visit(element)); + return context.privDefinition.map(element => this.visit(element)); + } + + organizationalSecurity(context) { + const res = context.NAME[0].image; + return { resource: res }; + } + + resourceDefinition(context) { + const res = context.NAME[0].image; + return { resource: res }; + } + + parentPrivilegesSecurity(context) { + // return _.map(context.parentPriveleges, element => this.visit(element)); + + const ent1 = context.parentPrivileges[0].children.NAME[0].image; + const fld = context.parentPrivileges[0].children.NAME[1].image; + + return { parent: ent1, field: fld }; + } + + relPrivilegesSecurity(context) { + return this.relPrivileges(context.relPrivileges[0].children); + } + + roleDefinition(context) { + const role = context.NAME[0].image; + let actionList = []; + if (context.rolePropList) { + // actionList = context.enumPropList[0].children.NAME.map(nameToken => nameToken.image); + if (context.rolePropList[0].children.NAME) { + actionList = context.rolePropList[0].children.NAME.map(nameToken => nameToken.image.toUpperCase()); + } + } + + return { role, actionList }; + } + + privDefinition(context) { + const action = context.NAME[0].image.toLowerCase(); + let privList = []; + if (context.rolePropList) { + // privList = context.enumPropList[0].children.NAME.map(nameToken => nameToken.image); + privList = context.rolePropList[0].children.NAME.map(nameToken => nameToken.image); + } + + return { action, privList }; + } + + parentPrivileges(context) { + const ent1 = context.NAME[0].image; + const fld = context.NAME[1].image; + + return { parent: ent1, field: fld }; + } + + relPrivileges(context) { + const ent1 = context.NAME[0].image; + const fld1 = context.NAME[1].image; + const ent2 = context.NAME[2].image; + const fld2 = context.NAME[3].image; + + return { fromEntity: ent1, fromField: fld1, toEntity: ent2, toField: fld2 }; + } + + secureEntityList(context) { + let entityList: any[] = []; + if (context.NAME) { + entityList = context.NAME.map(nameToken => nameToken.image); + } + + const entityOnlyListContainsAll = entityList.length === 1 && entityList[0] === 'all'; + // if (context.ALL || context.STAR) { + if (context.STAR || entityOnlyListContainsAll) { + entityList = ['*']; + } + let exclusionList: any[] = []; + if (context.exclusion) { + exclusionList = context.exclusion[0].children.NAME.map(nameToken => nameToken.image); + } + return { entityNames: deduplicate(entityList), excludedNames: deduplicate(exclusionList) }; + } + annotationDeclaration(context) { if (!context.value) { return { optionName: context.option[0].image, type: 'UNARY' }; @@ -374,6 +522,23 @@ export default class JDLAstBuilderVisitor extends BaseJDLCSTVisitor { return prop; } + rolePropList(context) { + return context.roleProp.map(this.visit, this); + } + + roleProp(context) { + const prop: any = { + key: context.rolePropKey[0].image, + }; + if (context.rolePropValue) { + prop.value = context.rolePropValue[0].image; + } + if (context.enumPropValueWithQuotes) { + prop.value = context.rolePropValueWithQuotes[0].image.replace(/"/g, ''); + } + return prop; + } + entityList(context) { let entityList: any[] = []; if (context.NAME) { diff --git a/jdl/parsing/jdl-parser.ts b/jdl/parsing/jdl-parser.ts index f712ce14c8c7..e8db218b9bf2 100644 --- a/jdl/parsing/jdl-parser.ts +++ b/jdl/parsing/jdl-parser.ts @@ -55,6 +55,19 @@ export default class JDLParser extends CstParser { this.enumDeclaration(); this.enumPropList(); this.enumProp(); + this.rolePropList(); + this.roleProp(); + this.secureDeclaration(); + this.rolesSecurity(); + this.privilegesSecurity(); + this.organizationalSecurity(); + this.parentPrivilegesSecurity(); + this.relPrivilegesSecurity(); + this.roleDefinition(); + this.privDefinition(); + this.parentPrivileges(); + this.relPrivileges(); + this.secureEntityList(); this.entityList(); this.exclusion(); this.useOptionDeclaration(); @@ -87,6 +100,7 @@ export default class JDLParser extends CstParser { { ALT: () => this.SUBRULE(this.entityDeclaration) }, { ALT: () => this.SUBRULE(this.relationDeclaration) }, { ALT: () => this.SUBRULE(this.enumDeclaration) }, + { ALT: () => this.SUBRULE(this.secureDeclaration) }, { ALT: () => this.CONSUME(LexerTokens.JAVADOC) }, { ALT: () => this.SUBRULE(this.useOptionDeclaration) }, { ALT: () => this.SUBRULE(this.unaryOptionDeclaration) }, @@ -371,6 +385,171 @@ export default class JDLParser extends CstParser { }); } + secureDeclaration(): any { + this.RULE('secureDeclaration', () => { + this.CONSUME(LexerTokens.SECURE); + this.SUBRULE(this.secureEntityList); + this.OR([ + { ALT: () => this.SUBRULE(this.rolesSecurity) }, + { ALT: () => this.SUBRULE(this.privilegesSecurity) }, + { ALT: () => this.SUBRULE(this.organizationalSecurity) }, + { ALT: () => this.SUBRULE(this.parentPrivilegesSecurity) }, + { ALT: () => this.SUBRULE(this.relPrivilegesSecurity) }, + ]); + }); + } + + rolesSecurity(): any { + this.RULE('rolesSecurity', () => { + this.CONSUME(LexerTokens.ROLES); + this.CONSUME(LexerTokens.LCURLY); + this.MANY(() => { + this.SUBRULE(this.roleDefinition); + this.OPTION(() => { + this.CONSUME(LexerTokens.COMMA); + }); + }); + this.CONSUME(LexerTokens.RCURLY); + }); + } + + privilegesSecurity(): any { + this.RULE('privilegesSecurity', () => { + this.CONSUME(LexerTokens.PRIVILEGES); + this.CONSUME(LexerTokens.LCURLY); + this.MANY(() => { + this.SUBRULE(this.privDefinition); + this.OPTION(() => { + this.CONSUME(LexerTokens.COMMA); + }); + }); + this.CONSUME(LexerTokens.RCURLY); + }); + } + + organizationalSecurity(): any { + this.RULE('organizationalSecurity', () => { + this.CONSUME(LexerTokens.ORGANIZATIONAL_SECURITY); + this.CONSUME(LexerTokens.LCURLY); + + this.OPTION(() => { + this.SUBRULE(this.comment); + }); + this.CONSUME(LexerTokens.RESOURCE_NAME); + this.CONSUME(LexerTokens.NAME); + this.CONSUME(LexerTokens.RCURLY); + }); + } + + parentPrivilegesSecurity(): any { + this.RULE('parentPrivilegesSecurity', () => { + this.CONSUME(LexerTokens.PARENT_PRIVILEGES); + this.CONSUME(LexerTokens.LCURLY); + this.SUBRULE(this.parentPrivileges); + this.CONSUME(LexerTokens.RCURLY); + }); + } + + relPrivilegesSecurity(): any { + this.RULE('relPrivilegesSecurity', () => { + this.CONSUME(LexerTokens.REL_PRIVILEGES); + this.CONSUME(LexerTokens.LCURLY); + this.SUBRULE(this.relPrivileges); + this.CONSUME(LexerTokens.RCURLY); + }); + } + + rolePropList(): any { + this.RULE('rolePropList', () => { + this.CONSUME(LexerTokens.LPAREN); + this.MANY_SEP({ + SEP: LexerTokens.COMMA, + DEF: () => { + this.CONSUME(LexerTokens.NAME); + }, + }); + this.CONSUME(LexerTokens.RPAREN); + }); + } + + roleProp(): any { + this.RULE('roleProp', () => { + this.OPTION(() => { + this.OR([ + { ALT: () => this.CONSUME2(LexerTokens.STRING, { LABEL: 'rolePropValueWithQuotes' }) }, + { ALT: () => this.CONSUME3(LexerTokens.NAME, { LABEL: 'rolePropValue' }) }, + ]); + }); + }); + } + + roleDefinition(): any { + this.RULE('roleDefinition', () => { + this.OPTION(() => { + this.SUBRULE(this.comment); + }); + this.CONSUME(LexerTokens.NAME); + this.CONSUME(LexerTokens.ALLOWS); + this.SUBRULE(this.rolePropList); + }); + } + + privDefinition(): any { + this.RULE('privDefinition', () => { + this.OPTION(() => { + this.SUBRULE(this.comment); + }); + this.CONSUME(LexerTokens.NAME); + this.CONSUME(LexerTokens.REQUIRE_PRIVS); + this.SUBRULE(this.rolePropList); + }); + } + + parentPrivileges(): any { + this.RULE('parentPrivileges', () => { + this.OPTION(() => { + this.SUBRULE(this.comment); + }); + this.CONSUME(LexerTokens.PARENT); + this.CONSUME(LexerTokens.NAME); + this.CONSUME(LexerTokens.FIELD); + this.CONSUME2(LexerTokens.NAME); + }); + } + + relPrivileges(): any { + this.RULE('relPrivileges', () => { + this.OPTION(() => { + this.SUBRULE(this.comment); + }); + this.CONSUME(LexerTokens.FROM_ENTITY); + this.CONSUME(LexerTokens.NAME); + this.CONSUME(LexerTokens.FIELD); + this.CONSUME2(LexerTokens.NAME); + this.CONSUME(LexerTokens.TO_ENTITY); + this.CONSUME3(LexerTokens.NAME); + this.CONSUME2(LexerTokens.FIELD); + this.CONSUME4(LexerTokens.NAME); + }); + } + + secureEntityList(): any { + this.RULE('secureEntityList', () => { + this.MANY({ + GATE: () => this.LA(2).tokenType === LexerTokens.COMMA, + DEF: () => { + this.CONSUME(LexerTokens.NAME); + this.CONSUME(LexerTokens.COMMA); + }, + }); + this.OR([{ ALT: () => this.CONSUME(LexerTokens.STAR) }, { ALT: () => this.CONSUME1(LexerTokens.NAME) }]); + this.OPTION(() => { + this.SUBRULE(this.exclusion); + }); + this.CONSUME(LexerTokens.WITH); + }); + } + entityList(): any { this.RULE('entityList', () => { this.commonEntityList(); diff --git a/jdl/parsing/lexer/lexer.ts b/jdl/parsing/lexer/lexer.ts index da265ceda71d..eabbbae38976 100644 --- a/jdl/parsing/lexer/lexer.ts +++ b/jdl/parsing/lexer/lexer.ts @@ -106,6 +106,21 @@ createTokenFromConfig({ name: 'ENUM', pattern: 'enum' }); // Relationship-related createTokenFromConfig({ name: 'RELATIONSHIP', pattern: 'relationship' }); createTokenFromConfig({ name: 'BUILT_IN_ENTITY', pattern: BUILT_IN_ENTITY }); +// Security +createTokenFromConfig({ name: 'SECURE', pattern: 'secure' }); +createTokenFromConfig({ name: 'ROLES', pattern: 'roles' }); +createTokenFromConfig({ name: 'CUSTOM_SECURITY', pattern: 'privilegesSecurity' }); +createTokenFromConfig({ name: 'ALLOWS', pattern: 'allows' }); +createTokenFromConfig({ name: 'REQUIRE_PRIVS', pattern: 'requirePrivs' }); +createTokenFromConfig({ name: 'PRIVILEGES', pattern: 'privileges' }); +createTokenFromConfig({ name: 'PARENT_PRIVILEGES', pattern: 'parentPrivileges' }); +createTokenFromConfig({ name: 'REL_PRIVILEGES', pattern: 'relPrivileges' }); +createTokenFromConfig({ name: 'ORGANIZATIONAL_SECURITY', pattern: 'organizationalSecurity' }); +createTokenFromConfig({ name: 'RESOURCE_NAME', pattern: 'resourceName' }); +createTokenFromConfig({ name: 'PARENT', pattern: 'parent' }); +createTokenFromConfig({ name: 'FROM_ENTITY', pattern: 'fromEntity' }); +createTokenFromConfig({ name: 'TO_ENTITY', pattern: 'toEntity' }); +createTokenFromConfig({ name: 'FIELD', pattern: 'field' }); // Category For the relationship type key names RelationshipTypeTokens.tokens.forEach(token => { diff --git a/jdl/parsing/validator.ts b/jdl/parsing/validator.ts index 906c3742ef3c..ff4f814e79cb 100644 --- a/jdl/parsing/validator.ts +++ b/jdl/parsing/validator.ts @@ -521,6 +521,18 @@ class JDLSyntaxValidatorVisitor extends BaseJDLCSTVisitorWithDefaults { }); } + rolePropList(context) { + super.rolePropList(context); + if (context.NAME && context.NAME.length > 0) { + context.NAME.forEach(nameToken => { + const propValue = nameToken.image; + if (propValue) { + this.checkNameSyntax(propValue, ENUM_PROP_VALUE_PATTERN, 'role property value'); + } + }); + } + } + entityList(context) { super.entityList(context); if (context.NAME) { diff --git a/jdl/utils/object-utils.ts b/jdl/utils/object-utils.ts index 78fa9c7d7f0c..474ed5762114 100644 --- a/jdl/utils/object-utils.ts +++ b/jdl/utils/object-utils.ts @@ -48,6 +48,20 @@ function removeEntriesWithUndefinedValue(entity: any) { }); } +function checkIfSecurityDiffers(firstEntity, secondEntity) { + if (firstEntity.security) { + if (!secondEntity.security) { + return true; + } + } + if (secondEntity.security) { + if (!firstEntity.security) { + return true; + } + } + return false; +} + export function areEntitiesEqual(firstEntity: any, secondEntity: any) { removeEntriesWithUndefinedValue(firstEntity); removeEntriesWithUndefinedValue(secondEntity); @@ -56,17 +70,60 @@ export function areEntitiesEqual(firstEntity: any, secondEntity: any) { firstEntity.relationships.length !== secondEntity.relationships.length || firstEntity.javadoc !== secondEntity.javadoc || firstEntity.entityTableName !== secondEntity.entityTableName || - Object.keys(firstEntity).length !== Object.keys(secondEntity).length + Object.keys(firstEntity).length !== Object.keys(secondEntity).length || + checkIfSecurityDiffers(firstEntity, secondEntity) ) { return false; } return ( areFieldsEqual(firstEntity.fields, secondEntity.fields) && areRelationshipsEqual(firstEntity.relationships, secondEntity.relationships) && - areOptionsTheSame(firstEntity, secondEntity) + areOptionsTheSame(firstEntity, secondEntity) && + areSecurityOptionsAreTheSame(firstEntity, secondEntity) ); } +function areSecurityOptionsAreTheSame(firstEntity, secondEntity) { + let result = true; + if (firstEntity.secure && secondEntity.secure) { + result = firstEntity.secure.securityType === secondEntity.secure.securityType; + if (result) { + if (firstEntity.secure.securityType === 'roles') { + if (firstEntity.secure.roles && secondEntity.secure.roles) { + result = firstEntity.secure.roles.length === secondEntity.secure.roles.length; + if (result) { + for (let i = 0; i < firstEntity.secure.roles.length; i++) { + const role1 = firstEntity.secure.roles[i]; + const role2 = secondEntity.secure.roles[i]; + result = result && role1.role === role2.role; + if (result && role1.actionList && role2.actionList && role1.actionList.length === role2.actionList.length) + for (let j = 0; j < role1.actionList.length; j++) { + result = result && role1.actionList[j] === role2.actionList[j]; + } + } + } + } + } else if (firstEntity.secure.customSecurity && secondEntity.secure.customSecurity) { + result = firstEntity.secure.customSecurity.length === secondEntity.secure.customSecurity.length; + if (result) { + for (let i = 0; i < firstEntity.secure.customSecurity.length; i++) { + const item1 = firstEntity.secure.customSecurity[i]; + const item2 = secondEntity.secure.customSecurity[i]; + result = result && item1.action === item2.action; + if (result && item1.privList && item2.privList && item1.privList.length === item2.privList.length) + for (let j = 0; j < item1.privList.length; j++) { + result = result && item1.privList[j] === item2.privList[j]; + } + } + } + } + } + } else { + return !firstEntity.secure && !secondEntity.secure; + } + return result; +} + function areFieldsEqual(firstFields: any[], secondFields: any[]) { return firstFields.every((field, index) => { if (Object.keys(field).length !== Object.keys(secondFields[index]).length) { diff --git a/test-integration/samples/jdl-entities/app-roles-security.jdl b/test-integration/samples/jdl-entities/app-roles-security.jdl new file mode 100644 index 000000000000..3063a014cf3b --- /dev/null +++ b/test-integration/samples/jdl-entities/app-roles-security.jdl @@ -0,0 +1,195 @@ +application { + config { + applicationType monolith, + baseName jhipsterSampleApplication, + packageName tech.jhipster.sample, + authenticationType jwt, + prodDatabaseType postgresql, + buildTool maven, + searchEngine no, + testFrameworks [gatling, cypress], + clientFramework angular, + enableTranslation true, + nativeLanguage en, + languages [ en, fr ] + } + entities * +} + +entity Bank { + bankNumber Integer +} +entity BankAccount { + name String required + bankNumber Integer + agencyNumber Long + lastOperationDuration Float + meanOperationDuration Double + balance BigDecimal required + openingDay LocalDate + lastOperationDate Instant + active Boolean + accountType BankAccountType + attachment AnyBlob + description TextBlob +} +entity TheLabel { + labelName String required minlength(3) +} +entity Operation { + date Instant required + description String + amount BigDecimal required +} + +enum BankAccountType { + CHECKING (checking_account), + SAVINGS (savings_account), + LOAN (loan_account) +} + +entity Department { + name String required, + description TextBlob, + advertisement Blob, + logo ImageBlob +} + +/** + * JobHistory comment. + */ +entity JobHistory { + startDate ZonedDateTime, + endDate ZonedDateTime, + language Language +} + +enum Language { + FRENCH, ENGLISH, SPANISH +} + +enum JobType { + BOSS, SLAVE +} + +entity Job { + title String minlength(5) maxlength(25), + type JobType, + minSalary Long, + maxSalary Long +} + +/** + * The Employee entity. + * Second line in javadoc. + */ +entity Employee { + /** + * The firstname attribute. + */ + firstName String, + lastName String, + email String, + phoneNumber String, + hireDate ZonedDateTime, + salary Long, + commissionPct Long +} + +entity Location { + streetAddress String, + postalCode String, + city String, + stateProvince String +} + +entity Task { + title String, + description String +} + +entity GoldenBadge { + name String +} + +entity SilverBadge { + name String +} + +entity Identifier { + name String required unique +} + +entity Country { + name String +} + +entity Region { + name String +} + +relationship OneToOne { + Department{location} to Location, + Employee{user(login)} to @Id User with builtInEntity +} + +relationship OneToMany { + BankAccount{operation} to Operation{bankAccount(name)} +} +relationship ManyToOne { + BankAccount{user(login)} to User with builtInEntity +} +relationship ManyToMany { + Operation{theLabel(labelName)} to TheLabel{operation} +} + +relationship OneToMany { + /** + * A relationship + */ + Department{employee} to + /** + * Another side of the same relationship, + */ + Employee{department}, + Employee{job} to Job{emp(lastName)}, + Location{country} to Country, + Country{area(name)} to Region +} + +relationship ManyToOne { + Employee{manager(lastName)} to Employee, + Employee{sibag(name) required} to SilverBadge, + Employee{gobag(name) required} to GoldenBadge, + SilverBadge{iden(name) required} to Identifier, + GoldenBadge{iden(name) required} to Identifier +} + +relationship ManyToMany { + JobHistory{department} to Department{history}, + JobHistory{job} to Job{history}, + JobHistory{emp(firstName)} to Employee{history}, + Job{chore(title)} to Task{linkedJob(title)}, + Bank{account} to BankAccount{bank} +} + +dto BankAccount, Employee, Department, Location, Country, Region, SilverBadge, GoldenBadge, Identifier with mapstruct + +angularSuffix BankAccount with mySuffix +filter BankAccount, Employee +clientRootFolder BankAccount, TheLabel, Operation with test-root + +paginate TheLabel, Job with pagination +paginate Operation, JobHistory, Employee with infinite-scroll + +service TheLabel, Employee, Department, Region with serviceClass +service BankAccount, Location, Country with serviceImpl +secure all except Country, Region with roles { + ROLE_ADMIN allows (get, put, post, delete) + ROLE_USER allows (get, put, post, delete) +} +secure Country,Region with roles { + ROLE_ADMIN allows (get, put, post, delete) + ROLE_USER allows () +} +