From 62e8b47f1719b6d43b257da8dd5a6fd56940e895 Mon Sep 17 00:00:00 2001 From: Don Walizer <12420708+dwalizer@users.noreply.github.com> Date: Thu, 16 Nov 2023 12:02:06 -0500 Subject: [PATCH 01/10] #2473 Changed regex to match other validation regex (cherry picked from commit e419dca9c6722e1601c680706ecdcb1d1cfccfe7) --- dashboard/src/components/access/InviteUsersToProject.vue | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dashboard/src/components/access/InviteUsersToProject.vue b/dashboard/src/components/access/InviteUsersToProject.vue index 09149b9763..c84147c86f 100644 --- a/dashboard/src/components/access/InviteUsersToProject.vue +++ b/dashboard/src/components/access/InviteUsersToProject.vue @@ -99,7 +99,7 @@ limitations under the License. import AccessService from '@/components/access/AccessService'; import MsgBoxMixin from '@/components/utils/modal/MsgBoxMixin'; - const validEmail = /^[a-z0-9.]{1,64}@[a-z0-9.]{1,64}$/i; + const validEmail = /^[a-zA-Z0-9_!#$%&'*+/=?`{|}~^.-]{1,64}@[a-zA-Z0-9.-]{1,64}$/i; const stripNames = /<([^\s<>@]+@[^\s<>@]+)>/; export default { name: 'InviteUsersToProject', From 215baf8b68ee5dac7ddb2d3062be337712ce5f0c Mon Sep 17 00:00:00 2001 From: Don Walizer <12420708+dwalizer@users.noreply.github.com> Date: Wed, 15 Nov 2023 13:14:06 -0500 Subject: [PATCH 02/10] #2472 Fix bug with count of subject points (cherry picked from commit b8ce4275e577c65a71ebbd365d21d9b571d9072a) --- .../main/java/skills/storage/repos/UserPointsRepo.groovy | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/service/src/main/java/skills/storage/repos/UserPointsRepo.groovy b/service/src/main/java/skills/storage/repos/UserPointsRepo.groovy index eed532d007..71b48a0d15 100644 --- a/service/src/main/java/skills/storage/repos/UserPointsRepo.groovy +++ b/service/src/main/java/skills/storage/repos/UserPointsRepo.groovy @@ -689,15 +689,15 @@ interface UserPointsRepo extends CrudRepository { ) SELECT COUNT(*) FROM ( - SELECT DISTINCT up.user_id + SELECT up.user_id, SUM(up.points) as total_points from user_points up, user_attrs usattr where up.user_id = usattr.user_id and up.skill_ref_id in (select id from subj_skills) and - up.points >= :minimumPoints and (lower(CONCAT(usattr.first_name, ' ', usattr.last_name, ' (', usattr.user_id_for_display, ')')) like lower(CONCAT('%', :userId, '%')) OR lower(usattr.user_id_for_display) like lower(CONCAT('%', :userId, '%'))) - ) AS temp + group by up.user_id + ) AS temp WHERE total_points >= :minimumPoints ''', nativeQuery = true) Long countDistinctUsersByProjectIdAndSubjectIdAndUserIdLike(@Param("projectId") String projectId, @Param("subjectId") String subjectId, @Param("userId") String userId, @Param("minimumPoints") int minimumPoints) From dbe4d58c7beae334fae7c98b506da824785690de Mon Sep 17 00:00:00 2001 From: Don Walizer <12420708+dwalizer@users.noreply.github.com> Date: Wed, 15 Nov 2023 14:10:23 -0500 Subject: [PATCH 03/10] #2472 Modify test to check for proper filtering on subjects (cherry picked from commit fca27b887b887bd5f0e807322573c62e15ad61a9) --- .../skills/intTests/UserPointsSpecs.groovy | 37 ++++++++++++++----- 1 file changed, 27 insertions(+), 10 deletions(-) diff --git a/service/src/test/java/skills/intTests/UserPointsSpecs.groovy b/service/src/test/java/skills/intTests/UserPointsSpecs.groovy index 0278a2a848..794b29b5a1 100644 --- a/service/src/test/java/skills/intTests/UserPointsSpecs.groovy +++ b/service/src/test/java/skills/intTests/UserPointsSpecs.groovy @@ -33,12 +33,13 @@ class UserPointsSpecs extends DefaultIntSpec { String projId = SkillsFactory.defaultProjId - List sampleUserIds = ['haNson', 'haRry', 'tom'] + List sampleUserIds = ['haNson', 'haRry', 'tom', 'user4', 'user5', 'user6', 'user7'] List subjects List> allSkillIds String badgeId Date threeDaysAgo = new Date()-3 + Date twoDaysAgo = new Date()-2 DateTimeFormatter DTF = DateTimeFormat.forPattern("yyyy-MM-dd'T'HH:mm:ss.SSSZZ").withZoneUTC() String ultimateRoot = 'jh@dojo.com' @@ -120,7 +121,7 @@ class UserPointsSpecs extends DefaultIntSpec { then: results - results.totalPoints == 9 * 35 + results.totalPoints == 9 * 35 * 4 results.count == 2 results.totalCount == 2 results.data.size() == 2 @@ -284,24 +285,40 @@ class UserPointsSpecs extends DefaultIntSpec { } def 'get subject users with minimum points'() { + skillsService.addSkill(['projectId': projId, skillId: allSkillIds.get(1).get(0)], sampleUserIds.get(2), threeDaysAgo) + skillsService.addSkill(['projectId': projId, skillId: allSkillIds.get(1).get(0)], sampleUserIds.get(3), threeDaysAgo) + skillsService.addSkill(['projectId': projId, skillId: allSkillIds.get(1).get(0)], sampleUserIds.get(4), threeDaysAgo) + skillsService.addSkill(['projectId': projId, skillId: allSkillIds.get(1).get(0)], sampleUserIds.get(5), threeDaysAgo) + skillsService.addSkill(['projectId': projId, skillId: allSkillIds.get(1).get(0)], sampleUserIds.get(2), twoDaysAgo) + skillsService.addSkill(['projectId': projId, skillId: allSkillIds.get(1).get(0)], sampleUserIds.get(3), twoDaysAgo) + skillsService.addSkill(['projectId': projId, skillId: allSkillIds.get(1).get(0)], sampleUserIds.get(4), twoDaysAgo) + skillsService.addSkill(['projectId': projId, skillId: allSkillIds.get(1).get(0)], sampleUserIds.get(5), twoDaysAgo) + when: def results1 = skillsService.getSubjectUsers(projId, subjects.get(1), 10, 1, "userId", true, "", 0) def results2 = skillsService.getSubjectUsers(projId, subjects.get(1), 10, 1, "userId", true, "", 40) + def results3 = skillsService.getSubjectUsers(projId, subjects.get(1), 10, 1, "userId", true, "", 80) then: results1 - results1.count == 2 - results1.totalCount == 2 - results1.data.size() == 2 + results1.count == 6 + results1.totalCount == 6 + results1.data.size() == 6 results1.data.get(0).userId.contains(sampleUserIds.get(0)?.toLowerCase()) results1.data.get(0).totalPoints == 35 results1.data.get(1).userId.contains(sampleUserIds.get(1)?.toLowerCase()) results1.data.get(1).totalPoints == 35 results2 - results2.count == 0 - results2.totalCount == 2 - results2.data.size() == 0 + results2.count == 4 + results2.totalCount == 6 + results2.data.size() == 4 + results2.data.get(0).totalPoints == 70 + + results3 + results3.count == 0 + results3.totalCount == 6 + results3.data.size() == 0 } def 'get skill users when project exists'() { @@ -400,7 +417,7 @@ class UserPointsSpecs extends DefaultIntSpec { results1.data.size() == 1 results1.data.get(0).userId.contains(sampleUserIds.get(0)?.toLowerCase()) results1.data.get(0).totalPoints == 35 - results1.totalPoints == 35 + results1.totalPoints == 35 * 4 } def "user updated date is updated when a skill is achieved"() { @@ -554,7 +571,7 @@ class UserPointsSpecs extends DefaultIntSpec { skillsService.createProject([projectId: projectId, name: name]) subjects.eachWithIndex { String subject, int index -> skillsService.createSubject([projectId: projectId, subjectId: subject, name: "Test Subject $index".toString()]) - skillIds << addDependentSkills(projectId, subject, 3) + skillIds << addDependentSkills(projectId, subject, 3, 1, 4) } return skillIds } From 58acbfe825930988cd9c91ebdf7d3b76568ad017 Mon Sep 17 00:00:00 2001 From: Don Walizer <12420708+dwalizer@users.noreply.github.com> Date: Wed, 15 Nov 2023 14:20:42 -0500 Subject: [PATCH 04/10] #2472 Add test case for minimum points for skills (cherry picked from commit 2c0288957e725cd4bc626dd095bc87a7f7900846) --- .../skills/intTests/UserPointsSpecs.groovy | 23 +++++++++++++++---- 1 file changed, 18 insertions(+), 5 deletions(-) diff --git a/service/src/test/java/skills/intTests/UserPointsSpecs.groovy b/service/src/test/java/skills/intTests/UserPointsSpecs.groovy index 794b29b5a1..8f8e180ffb 100644 --- a/service/src/test/java/skills/intTests/UserPointsSpecs.groovy +++ b/service/src/test/java/skills/intTests/UserPointsSpecs.groovy @@ -380,21 +380,34 @@ class UserPointsSpecs extends DefaultIntSpec { } def 'get skill users with minimum points'() { + skillsService.addSkill(['projectId': projId, skillId: allSkillIds.get(0).get(0)], sampleUserIds.get(2), threeDaysAgo) + skillsService.addSkill(['projectId': projId, skillId: allSkillIds.get(0).get(0)], sampleUserIds.get(3), threeDaysAgo) + skillsService.addSkill(['projectId': projId, skillId: allSkillIds.get(0).get(0)], sampleUserIds.get(4), threeDaysAgo) + skillsService.addSkill(['projectId': projId, skillId: allSkillIds.get(0).get(0)], sampleUserIds.get(5), threeDaysAgo) + skillsService.addSkill(['projectId': projId, skillId: allSkillIds.get(0).get(0)], sampleUserIds.get(2), twoDaysAgo) + skillsService.addSkill(['projectId': projId, skillId: allSkillIds.get(0).get(0)], sampleUserIds.get(3), twoDaysAgo) + skillsService.addSkill(['projectId': projId, skillId: allSkillIds.get(0).get(0)], sampleUserIds.get(4), twoDaysAgo) + when: def results1 = skillsService.getSkillUsers(projId, allSkillIds.get(0).get(0), 10, 1, "userId", true, "", 0) def results2 = skillsService.getSkillUsers(projId, allSkillIds.get(0).get(0), 10, 1, "userId", true, "", 45) + def results3 = skillsService.getSkillUsers(projId, allSkillIds.get(0).get(0), 10, 1, "userId", true, "", 80) then: results1 - results1.count == 1 - results1.totalCount == 1 - results1.data.size() == 1 + results1.count == 5 + results1.totalCount == 5 + results1.data.size() == 5 results1.data.get(0).userId.contains(sampleUserIds.get(0)?.toLowerCase()) results1.data.get(0).totalPoints == 35 results2 - results2.count == 0 - results2.totalCount == 1 + results2.count == 3 + results2.totalCount == 5 + + results3 + results3.count == 0 + results3.totalCount == 5 } def 'can not get skills with negative points'() { From 3367ac5fc16b5cf3f4c065a5cb89390761c337c4 Mon Sep 17 00:00:00 2001 From: Don Walizer <12420708+dwalizer@users.noreply.github.com> Date: Thu, 16 Nov 2023 09:35:24 -0500 Subject: [PATCH 05/10] #2472 Modify tests (cherry picked from commit f7800ff1da828c400528d09c6a6b7dbb177f5100) --- .../skills/intTests/UserPointsSpecs.groovy | 29 +++++++++---------- 1 file changed, 13 insertions(+), 16 deletions(-) diff --git a/service/src/test/java/skills/intTests/UserPointsSpecs.groovy b/service/src/test/java/skills/intTests/UserPointsSpecs.groovy index 8f8e180ffb..e516d881f6 100644 --- a/service/src/test/java/skills/intTests/UserPointsSpecs.groovy +++ b/service/src/test/java/skills/intTests/UserPointsSpecs.groovy @@ -33,7 +33,7 @@ class UserPointsSpecs extends DefaultIntSpec { String projId = SkillsFactory.defaultProjId - List sampleUserIds = ['haNson', 'haRry', 'tom', 'user4', 'user5', 'user6', 'user7'] + List sampleUserIds = ['haNson', 'haRry', 'tom', 'user4', 'user5'] List subjects List> allSkillIds String badgeId @@ -288,11 +288,9 @@ class UserPointsSpecs extends DefaultIntSpec { skillsService.addSkill(['projectId': projId, skillId: allSkillIds.get(1).get(0)], sampleUserIds.get(2), threeDaysAgo) skillsService.addSkill(['projectId': projId, skillId: allSkillIds.get(1).get(0)], sampleUserIds.get(3), threeDaysAgo) skillsService.addSkill(['projectId': projId, skillId: allSkillIds.get(1).get(0)], sampleUserIds.get(4), threeDaysAgo) - skillsService.addSkill(['projectId': projId, skillId: allSkillIds.get(1).get(0)], sampleUserIds.get(5), threeDaysAgo) skillsService.addSkill(['projectId': projId, skillId: allSkillIds.get(1).get(0)], sampleUserIds.get(2), twoDaysAgo) skillsService.addSkill(['projectId': projId, skillId: allSkillIds.get(1).get(0)], sampleUserIds.get(3), twoDaysAgo) skillsService.addSkill(['projectId': projId, skillId: allSkillIds.get(1).get(0)], sampleUserIds.get(4), twoDaysAgo) - skillsService.addSkill(['projectId': projId, skillId: allSkillIds.get(1).get(0)], sampleUserIds.get(5), twoDaysAgo) when: def results1 = skillsService.getSubjectUsers(projId, subjects.get(1), 10, 1, "userId", true, "", 0) @@ -301,23 +299,23 @@ class UserPointsSpecs extends DefaultIntSpec { then: results1 - results1.count == 6 - results1.totalCount == 6 - results1.data.size() == 6 + results1.count == 5 + results1.totalCount == 5 + results1.data.size() == 5 results1.data.get(0).userId.contains(sampleUserIds.get(0)?.toLowerCase()) results1.data.get(0).totalPoints == 35 results1.data.get(1).userId.contains(sampleUserIds.get(1)?.toLowerCase()) results1.data.get(1).totalPoints == 35 results2 - results2.count == 4 - results2.totalCount == 6 - results2.data.size() == 4 + results2.count == 3 + results2.totalCount == 5 + results2.data.size() == 3 results2.data.get(0).totalPoints == 70 results3 results3.count == 0 - results3.totalCount == 6 + results3.totalCount == 5 results3.data.size() == 0 } @@ -383,7 +381,6 @@ class UserPointsSpecs extends DefaultIntSpec { skillsService.addSkill(['projectId': projId, skillId: allSkillIds.get(0).get(0)], sampleUserIds.get(2), threeDaysAgo) skillsService.addSkill(['projectId': projId, skillId: allSkillIds.get(0).get(0)], sampleUserIds.get(3), threeDaysAgo) skillsService.addSkill(['projectId': projId, skillId: allSkillIds.get(0).get(0)], sampleUserIds.get(4), threeDaysAgo) - skillsService.addSkill(['projectId': projId, skillId: allSkillIds.get(0).get(0)], sampleUserIds.get(5), threeDaysAgo) skillsService.addSkill(['projectId': projId, skillId: allSkillIds.get(0).get(0)], sampleUserIds.get(2), twoDaysAgo) skillsService.addSkill(['projectId': projId, skillId: allSkillIds.get(0).get(0)], sampleUserIds.get(3), twoDaysAgo) skillsService.addSkill(['projectId': projId, skillId: allSkillIds.get(0).get(0)], sampleUserIds.get(4), twoDaysAgo) @@ -395,19 +392,19 @@ class UserPointsSpecs extends DefaultIntSpec { then: results1 - results1.count == 5 - results1.totalCount == 5 - results1.data.size() == 5 + results1.count == 4 + results1.totalCount == 4 + results1.data.size() == 4 results1.data.get(0).userId.contains(sampleUserIds.get(0)?.toLowerCase()) results1.data.get(0).totalPoints == 35 results2 results2.count == 3 - results2.totalCount == 5 + results2.totalCount == 4 results3 results3.count == 0 - results3.totalCount == 5 + results3.totalCount == 4 } def 'can not get skills with negative points'() { From 9a3da5503ccbe12109bb352409d2b9eb63b3d1c8 Mon Sep 17 00:00:00 2001 From: "EVOFORGE\\dimay" Date: Tue, 21 Nov 2023 10:32:25 -0500 Subject: [PATCH 06/10] #2484: allow quiz questions answer to be "null" word; added validation for every modal to ensure that "null" value is not allowed as items' names and ids --- dashboard/src/components/badges/EditBadge.vue | 2 +- .../src/components/projects/EditProject.vue | 2 +- .../quiz/testCreation/EditQuestion.vue | 2 +- .../components/quiz/testCreation/EditQuiz.vue | 2 +- dashboard/src/components/skills/EditSkill.vue | 2 +- .../skills/skillsGroup/EditSkillGroup.vue | 2 +- .../src/components/subjects/EditSubject.vue | 2 +- .../components/utils/inputForm/IdInput.vue | 2 +- dashboard/src/validators/NotNullValidator.js | 28 +++++++++++++++++ .../src/validators/RegisterValidators.js | 1 + .../e2e/projects_modal_validation_spec.js | 30 +++++++++++++++++++ .../skills/controller/AdminController.groovy | 2 +- .../exceptions/QuizValidator.groovy | 4 +-- .../exceptions/SkillsValidator.groovy | 4 +-- .../services/quiz/QuizDefService.groovy | 2 +- .../intTests/ConstraintViolationSpecs.groovy | 9 ++++++ .../intTests/quiz/QuizApi_RunQuizSpecs.groovy | 25 +++++++++++++++- 17 files changed, 106 insertions(+), 15 deletions(-) create mode 100644 dashboard/src/validators/NotNullValidator.js diff --git a/dashboard/src/components/badges/EditBadge.vue b/dashboard/src/components/badges/EditBadge.vue index acad8e815a..5b1f8c771c 100644 --- a/dashboard/src/components/badges/EditBadge.vue +++ b/dashboard/src/components/badges/EditBadge.vue @@ -32,7 +32,7 @@ limitations under the License.
-
- diff --git a/dashboard/src/components/quiz/testCreation/EditQuestion.vue b/dashboard/src/components/quiz/testCreation/EditQuestion.vue index 24a2f0f3be..84e5e00e88 100644 --- a/dashboard/src/components/quiz/testCreation/EditQuestion.vue +++ b/dashboard/src/components/quiz/testCreation/EditQuestion.vue @@ -25,7 +25,7 @@ limitations under the License. - + diff --git a/dashboard/src/components/skills/EditSkill.vue b/dashboard/src/components/skills/EditSkill.vue index 13a63974a2..6ace62af80 100644 --- a/dashboard/src/components/skills/EditSkill.vue +++ b/dashboard/src/components/skills/EditSkill.vue @@ -26,7 +26,7 @@ limitations under the License.
- +
- diff --git a/dashboard/src/components/subjects/EditSubject.vue b/dashboard/src/components/subjects/EditSubject.vue index 5a9a434ede..8feb150036 100644 --- a/dashboard/src/components/subjects/EditSubject.vue +++ b/dashboard/src/components/subjects/EditSubject.vue @@ -35,7 +35,7 @@ limitations under the License.
`Null is not allowed for ${field}`, + validate(value) { + console.log(value); + return !value || value.toLowerCase() !== 'null'; + }, +}; + +extend('nullValueNotAllowed', validator); + +export default validator; diff --git a/dashboard/src/validators/RegisterValidators.js b/dashboard/src/validators/RegisterValidators.js index c2105e12bf..735314f729 100644 --- a/dashboard/src/validators/RegisterValidators.js +++ b/dashboard/src/validators/RegisterValidators.js @@ -21,6 +21,7 @@ import './OptionalNumericValidator'; import './CustomNameValidator'; import './IdValidator'; import './SkillIdValidator'; +import './NotNullValidator'; import './UrlValidator'; import store from '../store/store'; diff --git a/e2e-tests/cypress/e2e/projects_modal_validation_spec.js b/e2e-tests/cypress/e2e/projects_modal_validation_spec.js index d3d68fb625..3593bc86ae 100644 --- a/e2e-tests/cypress/e2e/projects_modal_validation_spec.js +++ b/e2e-tests/cypress/e2e/projects_modal_validation_spec.js @@ -342,4 +342,34 @@ describe('Projects Modal Validation Tests', () => { cy.get('[data-cy="projectDescriptionError"]').contains('Mocked up validation failure') }); + it('null word is not allowed for project ID or project name', () => { + cy.visit('/administrator/'); + cy.get('[data-cy="newProjectButton"]').click() + cy.get('[data-cy="projectName"]') + .type('null'); + cy.get('[data-cy=projectNameError]') + .contains('Null is not allowed for Project Name') + .should('be.visible'); + cy.get('[data-cy="idError"]') + .contains('Null is not allowed for Project ID') + .should('be.visible'); + cy.get('[data-cy=saveProjectButton]') + .should('be.disabled'); + cy.get('[data-cy="projectName"]').clear() + cy.get('[data-cy=projectNameError]').should('not.be.visible') + cy.get('[data-cy=idError]').should('not.be.visible') + + // verify validator is case insensitive + cy.get('[data-cy="projectName"]') + .type('NUlL'); + cy.get('[data-cy=projectNameError]') + .contains('Null is not allowed for Project Name') + .should('be.visible'); + cy.get('[data-cy="idError"]') + .contains('Null is not allowed for Project ID') + .should('be.visible'); + cy.get('[data-cy=saveProjectButton]') + .should('be.disabled'); + }); + }); \ No newline at end of file diff --git a/service/src/main/java/skills/controller/AdminController.groovy b/service/src/main/java/skills/controller/AdminController.groovy index 4d59ab8ac1..400804a554 100644 --- a/service/src/main/java/skills/controller/AdminController.groovy +++ b/service/src/main/java/skills/controller/AdminController.groovy @@ -329,7 +329,7 @@ class AdminController { @RequestBody NameExistsRequest existsRequest) { String subjectName = existsRequest.name?.trim() SkillsValidator.isNotBlank(projectId, "Project Id") - SkillsValidator.isNotBlank(subjectName, "Subject Name") + SkillsValidator.isNotBlank(subjectName, "Subject Name", projectId, null, true ) def sanitize = InputSanitizer.sanitize(subjectName) return subjAdminService.existsBySubjectName(InputSanitizer.sanitize(projectId), sanitize) diff --git a/service/src/main/java/skills/controller/exceptions/QuizValidator.groovy b/service/src/main/java/skills/controller/exceptions/QuizValidator.groovy index d2b28707df..3d5d634467 100644 --- a/service/src/main/java/skills/controller/exceptions/QuizValidator.groovy +++ b/service/src/main/java/skills/controller/exceptions/QuizValidator.groovy @@ -19,8 +19,8 @@ import org.apache.commons.lang3.StringUtils class QuizValidator { - static void isNotBlank(String value, String attrName, String quizId = null) { - if (StringUtils.isBlank(value) || value?.trim().equalsIgnoreCase("null")) { + static void isNotBlank(String value, String attrName, String quizId = null, boolean allowNullValueStr = false) { + if (StringUtils.isBlank(value) || (!allowNullValueStr && value?.trim().equalsIgnoreCase("null"))) { throw new SkillQuizException("${attrName} was not provided.".toString(), quizId, ErrorCode.BadParam) } } diff --git a/service/src/main/java/skills/controller/exceptions/SkillsValidator.groovy b/service/src/main/java/skills/controller/exceptions/SkillsValidator.groovy index 79ab5905c9..7f8927a0aa 100644 --- a/service/src/main/java/skills/controller/exceptions/SkillsValidator.groovy +++ b/service/src/main/java/skills/controller/exceptions/SkillsValidator.groovy @@ -19,8 +19,8 @@ import org.apache.commons.lang3.StringUtils class SkillsValidator { - static void isNotBlank(String value, String attrName, String projectId = null, String skillId = null) { - if (StringUtils.isBlank(value) || value?.trim().equalsIgnoreCase("null")) { + static void isNotBlank(String value, String attrName, String projectId = null, String skillId = null, boolean allowNullValueStr = false) { + if (StringUtils.isBlank(value) || (!allowNullValueStr && value?.trim().equalsIgnoreCase("null"))) { throw new SkillException("${attrName} was not provided.".toString(), projectId, skillId, ErrorCode.BadParam) } } diff --git a/service/src/main/java/skills/services/quiz/QuizDefService.groovy b/service/src/main/java/skills/services/quiz/QuizDefService.groovy index 5af3160563..2462b2e1a1 100644 --- a/service/src/main/java/skills/services/quiz/QuizDefService.groovy +++ b/service/src/main/java/skills/services/quiz/QuizDefService.groovy @@ -765,7 +765,7 @@ class QuizDefService { QuizValidator.isNotNull(questionDefRequest.answers, "answers", quizId) QuizValidator.isTrue(questionDefRequest.answers.size() >= 2, "Must have at least 2 answers", quizId) questionDefRequest.answers.each { - QuizValidator.isNotBlank(it.answer, "answers.answer", quizId) + QuizValidator.isNotBlank(it.answer, "answers.answer", quizId, true) propsBasedValidator.quizValidationMaxStrLength(PublicProps.UiProp.maxQuizTextAnswerLength, "Answer", it.answer, quizDef.quizId) } } diff --git a/service/src/test/java/skills/intTests/ConstraintViolationSpecs.groovy b/service/src/test/java/skills/intTests/ConstraintViolationSpecs.groovy index b06c78c3b5..2efa28a956 100644 --- a/service/src/test/java/skills/intTests/ConstraintViolationSpecs.groovy +++ b/service/src/test/java/skills/intTests/ConstraintViolationSpecs.groovy @@ -460,4 +460,13 @@ class ConstraintViolationSpecs extends DefaultIntSpec { exception.message.contains("errorCode:ConstraintViolation") } + def "subject name exists does not fail on the null word"() { + def proj = SkillsFactory.createProject(1) + skillsService.createProject(proj) + when: + def exist = skillsService.subjectNameExists([projectId: proj.projectId, subjectName: "null"]) + + then: + exist == false + } } diff --git a/service/src/test/java/skills/intTests/quiz/QuizApi_RunQuizSpecs.groovy b/service/src/test/java/skills/intTests/quiz/QuizApi_RunQuizSpecs.groovy index fc8fdb4917..b272cfe6d8 100644 --- a/service/src/test/java/skills/intTests/quiz/QuizApi_RunQuizSpecs.groovy +++ b/service/src/test/java/skills/intTests/quiz/QuizApi_RunQuizSpecs.groovy @@ -15,8 +15,8 @@ */ package skills.intTests.quiz + import org.springframework.beans.factory.annotation.Autowired -import skills.controller.exceptions.SkillQuizException import skills.intTests.utils.DefaultIntSpec import skills.intTests.utils.QuizDefFactory import skills.intTests.utils.SkillsClientException @@ -424,4 +424,27 @@ class QuizApi_RunQuizSpecs extends DefaultIntSpec { SkillsClientException quizException = thrown() quizException.message.contains("Deadline for [${quizAttempt.id}] has expired") } + + def "allow word NULL in as quiz answer"() { + def quiz = QuizDefFactory.createQuiz(1, "Fancy Description") + skillsService.createQuizDef(quiz) + def questions = QuizDefFactory.createChoiceQuestions(1, 2, 2) + questions[0].answers[0].answer = "Null" + questions[1].answers[0].answer = "NULL" + skillsService.createQuizQuestionDefs(questions) + + def quizInfo = skillsService.getQuizInfo(quiz.quizId) + when: + def quizAttempt = skillsService.startQuizAttempt(quiz.quizId).body + skillsService.reportQuizAnswer(quiz.quizId, quizAttempt.id, quizInfo.questions[0].answerOptions[0].id) + skillsService.reportQuizAnswer(quiz.quizId, quizAttempt.id, quizInfo.questions[1].answerOptions[0].id) + def gradedQuizAttempt = skillsService.completeQuizAttempt(quiz.quizId, quizAttempt.id).body + then: + gradedQuizAttempt.passed == true + gradedQuizAttempt.numQuestionsGotWrong == 0 + gradedQuizAttempt.gradedQuestions.questionId == quizInfo.questions.id + gradedQuizAttempt.gradedQuestions.isCorrect == [true, true] + gradedQuizAttempt.gradedQuestions[0].selectedAnswerIds == [quizInfo.questions[0].answerOptions[0].id] + gradedQuizAttempt.gradedQuestions[1].selectedAnswerIds == [quizInfo.questions[1].answerOptions[0].id] + } } From 589d10c21e65b4732ff163d45d14b73c385bf552 Mon Sep 17 00:00:00 2001 From: "EVOFORGE\\dimay" Date: Tue, 21 Nov 2023 13:02:27 -0500 Subject: [PATCH 07/10] #2484: allow quiz questions answer to be "null" word; added validation for every modal to ensure that "null" value is not allowed as items' names and ids --- dashboard/src/validators/NotNullValidator.js | 1 - 1 file changed, 1 deletion(-) diff --git a/dashboard/src/validators/NotNullValidator.js b/dashboard/src/validators/NotNullValidator.js index 2d1537de79..aa00b535a4 100644 --- a/dashboard/src/validators/NotNullValidator.js +++ b/dashboard/src/validators/NotNullValidator.js @@ -18,7 +18,6 @@ import { extend } from 'vee-validate'; const validator = { message: (field) => `Null is not allowed for ${field}`, validate(value) { - console.log(value); return !value || value.toLowerCase() !== 'null'; }, }; From 0ffb446b7d9a587b20f4d8a556028a63ca8a5ebd Mon Sep 17 00:00:00 2001 From: "EVOFORGE\\dimay" Date: Tue, 21 Nov 2023 14:49:10 -0500 Subject: [PATCH 08/10] #2484: fixed test --- e2e-tests/cypress/e2e/projects_modal_validation_spec.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/e2e-tests/cypress/e2e/projects_modal_validation_spec.js b/e2e-tests/cypress/e2e/projects_modal_validation_spec.js index 3593bc86ae..7862148ce7 100644 --- a/e2e-tests/cypress/e2e/projects_modal_validation_spec.js +++ b/e2e-tests/cypress/e2e/projects_modal_validation_spec.js @@ -355,12 +355,12 @@ describe('Projects Modal Validation Tests', () => { .should('be.visible'); cy.get('[data-cy=saveProjectButton]') .should('be.disabled'); - cy.get('[data-cy="projectName"]').clear() + cy.get('[data-cy="projectName"]').clear().type('one') cy.get('[data-cy=projectNameError]').should('not.be.visible') cy.get('[data-cy=idError]').should('not.be.visible') // verify validator is case insensitive - cy.get('[data-cy="projectName"]') + cy.get('[data-cy="projectName"]').clear() .type('NUlL'); cy.get('[data-cy=projectNameError]') .contains('Null is not allowed for Project Name') From 1f0c269028b4d00635cd2d82777a38fa96e82f89 Mon Sep 17 00:00:00 2001 From: "EVOFORGE\\dimay" Date: Wed, 22 Nov 2023 08:34:37 -0500 Subject: [PATCH 09/10] #2484: validated that all the 'name exist' endpoints can accept 'null' word and fixed badge and skill endpoints --- .../skills/controller/AdminController.groovy | 4 +- .../intTests/ConstraintViolationSpecs.groovy | 39 ++++++++++++++++++- 2 files changed, 39 insertions(+), 4 deletions(-) diff --git a/service/src/main/java/skills/controller/AdminController.groovy b/service/src/main/java/skills/controller/AdminController.groovy index 400804a554..7cf3aefdf8 100644 --- a/service/src/main/java/skills/controller/AdminController.groovy +++ b/service/src/main/java/skills/controller/AdminController.groovy @@ -342,7 +342,7 @@ class AdminController { @RequestBody NameExistsRequest nameExistsRequest) { String badgeName = nameExistsRequest.name?.trim() SkillsValidator.isNotBlank(projectId, "Project Id") - SkillsValidator.isNotBlank(badgeName, "Badge Name") + SkillsValidator.isNotBlank(badgeName, "Badge Name", projectId, null, true) return badgeAdminService.existsByBadgeName(InputSanitizer.sanitize(projectId), InputSanitizer.sanitize(badgeName)) } @@ -353,7 +353,7 @@ class AdminController { @RequestBody NameExistsRequest existsRequest) { String skillName = existsRequest.name?.trim() SkillsValidator.isNotBlank(projectId, "Project Id") - SkillsValidator.isNotBlank(skillName, "Skill Name") + SkillsValidator.isNotBlank(skillName, "Skill Name", projectId, null, true) return skillsAdminService.existsBySkillName(InputSanitizer.sanitize(projectId), InputSanitizer.sanitize(skillName)) } diff --git a/service/src/test/java/skills/intTests/ConstraintViolationSpecs.groovy b/service/src/test/java/skills/intTests/ConstraintViolationSpecs.groovy index 2efa28a956..1d5225a537 100644 --- a/service/src/test/java/skills/intTests/ConstraintViolationSpecs.groovy +++ b/service/src/test/java/skills/intTests/ConstraintViolationSpecs.groovy @@ -257,7 +257,7 @@ class ConstraintViolationSpecs extends DefaultIntSpec { Map proj = SkillsFactory.createProject() Map subject = SkillsFactory.createSubject() Map skill = SkillsFactory.createSkill() - Map skill2 = SkillsFactory.createSkill(1, 1, 2, ) + Map skill2 = SkillsFactory.createSkill(1, 1, 2,) Map copy = new HashMap(skill) copy.skillId = skill2.skillId.toUpperCase() copy.name = "somethingElse" @@ -469,4 +469,39 @@ class ConstraintViolationSpecs extends DefaultIntSpec { then: exist == false } -} + + def "project name exists does not fail on the null word"() { + when: + def exist = skillsService.projectNameExists([projectName: "null"]) + then: + exist == false + } + + def "badge name exists does not fail on the null word"() { + def proj = SkillsFactory.createProject(1) + skillsService.createProject(proj) + when: + def exist = skillsService.badgeNameExists([projectId: proj.projectId, badgeName: "null"]) + then: + exist == false + } + + def "skill name exists does not fail on the null word"() { + def proj = SkillsFactory.createProject(1) + skillsService.createProject(proj) + def subj = SkillsFactory.createSubject(1, 1) + skillsService.createSubject(subj) + when: + def exist = skillsService.skillNameExists([projectId: proj.projectId, skillName: "null"]) + then: + exist == false + } + + def "quiz name exists does not fail on the null word"() { + when: + def exist = skillsService.quizNameExist("null") + then: + exist.body == false + } + +} \ No newline at end of file From c047d86cd9b462854181715fff9d9ec9665c0406 Mon Sep 17 00:00:00 2001 From: "EVOFORGE\\dimay" Date: Wed, 22 Nov 2023 11:00:46 -0500 Subject: [PATCH 10/10] #2484: trim the string prior validation --- dashboard/src/validators/NotNullValidator.js | 2 +- e2e-tests/cypress/e2e/projects_modal_validation_spec.js | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/dashboard/src/validators/NotNullValidator.js b/dashboard/src/validators/NotNullValidator.js index aa00b535a4..677b965af6 100644 --- a/dashboard/src/validators/NotNullValidator.js +++ b/dashboard/src/validators/NotNullValidator.js @@ -18,7 +18,7 @@ import { extend } from 'vee-validate'; const validator = { message: (field) => `Null is not allowed for ${field}`, validate(value) { - return !value || value.toLowerCase() !== 'null'; + return !value || value.trim().toLowerCase() !== 'null'; }, }; diff --git a/e2e-tests/cypress/e2e/projects_modal_validation_spec.js b/e2e-tests/cypress/e2e/projects_modal_validation_spec.js index 7862148ce7..5a79b7bb33 100644 --- a/e2e-tests/cypress/e2e/projects_modal_validation_spec.js +++ b/e2e-tests/cypress/e2e/projects_modal_validation_spec.js @@ -359,9 +359,9 @@ describe('Projects Modal Validation Tests', () => { cy.get('[data-cy=projectNameError]').should('not.be.visible') cy.get('[data-cy=idError]').should('not.be.visible') - // verify validator is case insensitive + // verify validator is case-insensitive and trims whitespace cy.get('[data-cy="projectName"]').clear() - .type('NUlL'); + .type(' NUlL '); cy.get('[data-cy=projectNameError]') .contains('Null is not allowed for Project Name') .should('be.visible');