From ae72d6230d844493a7c2a80ce76311807bebd4c6 Mon Sep 17 00:00:00 2001 From: maggarwal13 <50230916+maggarwal13@users.noreply.github.com> Date: Mon, 15 Apr 2024 12:21:33 +0200 Subject: [PATCH] NWI-4: Add ALLOCATED-ASSIGNED PA inetnum status (#1432) * add_allocated_assigned_pa * add_tests * add_tests * fix tests * fix tests * add delete tests * add modify * fix compilation error * fix unit tests * add more tests * add tests * indentation * Remove IANA as it's not used as an org-type for ALLOCATED PA. * Fixed tests * Fixed tests --------- Co-authored-by: Ed Shryane --- .../db/whois/api/rest/GeolocationService.java | 1 + .../api/rest/GeolocationTestIntegration.java | 35 +++ .../common/search/ManagedAttributeSearch.java | 2 +- .../integration/InetnumIntegrationSpec.groovy | 250 +++++++++++++++- .../db/whois/spec/update/InetnumSpec.groovy | 126 ++++++++ .../spec/update/InetnumStatusChildSpec.groovy | 269 +++++++++++++++++- .../integration/TemplateTestIntegration.java | 1 + .../common/rpsl/attrs/InetnumStatus.java | 8 +- .../common/rpsl/attrs/InetnumStatusTest.java | 2 +- .../inetnum/InetnumStatusValidator.java | 15 +- .../inetnum/InetnumStrictStatusValidator.java | 22 +- .../validator/inetnum/LirMntByValidator.java | 4 +- .../InetnumStrictStatusValidatorTest.java | 2 + 13 files changed, 715 insertions(+), 22 deletions(-) diff --git a/whois-api/src/main/java/net/ripe/db/whois/api/rest/GeolocationService.java b/whois-api/src/main/java/net/ripe/db/whois/api/rest/GeolocationService.java index a2b900bdc2..87865d637e 100644 --- a/whois-api/src/main/java/net/ripe/db/whois/api/rest/GeolocationService.java +++ b/whois-api/src/main/java/net/ripe/db/whois/api/rest/GeolocationService.java @@ -55,6 +55,7 @@ public class GeolocationService { InetnumStatus.ASSIGNED_PI, InetnumStatus.ASSIGNED_ANYCAST, InetnumStatus.ALLOCATED_PA, + InetnumStatus.ALLOCATED_ASSIGNED_PA, InetnumStatus.ALLOCATED_UNSPECIFIED); private static final Set STOP_AT_STATUS_IPV6 = Sets.immutableEnumSet( diff --git a/whois-api/src/test/java/net/ripe/db/whois/api/rest/GeolocationTestIntegration.java b/whois-api/src/test/java/net/ripe/db/whois/api/rest/GeolocationTestIntegration.java index c04b830589..a51209287b 100644 --- a/whois-api/src/test/java/net/ripe/db/whois/api/rest/GeolocationTestIntegration.java +++ b/whois-api/src/test/java/net/ripe/db/whois/api/rest/GeolocationTestIntegration.java @@ -251,6 +251,41 @@ public void parent_inetnum_with_geolocation_and_language() throws Exception { assertThat(response, containsString("")); } + @Test + public void parent_inetnum_with_geolocation_allocated_Assigned_pa() throws Exception { + databaseHelper.addObject( + "inetnum: 10.0.0.0 - 10.255.255.255\n" + + "netname: RIPE-NCC\n" + + "language: EN\n" + + "geoloc: 52.375599 5.9888802\n" + + "descr: Private Network\n" + + "country: NL\n" + + "tech-c: TP1-TEST\n" + + "status: ALLOCATED UNSPECIFIED\n" + + "mnt-by: OWNER-MNT\n" + + "mnt-lower: OWNER-MNT\n" + + "source: TEST"); + databaseHelper.addObject( + "inetnum: 10.1.0.0 - 10.1.255.255\n" + + "netname: RIPE-NCC\n" + + "descr: Private Network\n" + + "geoloc: 52.375599 4.899902\n" + + "country: NL\n" + + "tech-c: TP1-TEST\n" + + "status: ALLOCATED-ASSIGNED PA\n" + + "mnt-by: OWNER-MNT\n" + + "mnt-lower: OWNER-MNT\n" + + "source: TEST"); + ipTreeUpdater.rebuild(); + + final String response = RestTest.target(getPort(), "whois/geolocation?ipkey=10.1.0.0%20-%2010.1.255.255") + .request(MediaType.APPLICATION_XML) + .get(String.class); + + assertThat(response, containsString("")); + assertThat(response, containsString("")); + } + @Test public void inet6num_with_geolocation_and_language() throws Exception { databaseHelper.addObject( diff --git a/whois-commons/src/main/java/net/ripe/db/whois/common/search/ManagedAttributeSearch.java b/whois-commons/src/main/java/net/ripe/db/whois/common/search/ManagedAttributeSearch.java index aad9412047..4304b267ea 100644 --- a/whois-commons/src/main/java/net/ripe/db/whois/common/search/ManagedAttributeSearch.java +++ b/whois-commons/src/main/java/net/ripe/db/whois/common/search/ManagedAttributeSearch.java @@ -34,7 +34,7 @@ public class ManagedAttributeSearch { private static final ImmutableSet AUT_NUM_ATTRIBUTES = Sets.immutableEnumSet(AttributeType.AUT_NUM, AttributeType.ORG, AttributeType.SPONSORING_ORG, AttributeType.STATUS, AttributeType.SOURCE); private static final ImmutableSet INETNUM_ASSIGNMENT_STATUSES = Sets.immutableEnumSet(InetnumStatus.ASSIGNED_PI, InetnumStatus.ASSIGNED_ANYCAST); - private static final ImmutableSet INETNUM_ALLOCATION_STATUSES = Sets.immutableEnumSet(InetnumStatus.ALLOCATED_PA, InetnumStatus.ALLOCATED_UNSPECIFIED); + private static final ImmutableSet INETNUM_ALLOCATION_STATUSES = Sets.immutableEnumSet(InetnumStatus.ALLOCATED_PA, InetnumStatus.ALLOCATED_ASSIGNED_PA, InetnumStatus.ALLOCATED_UNSPECIFIED); private static final ImmutableSet INETNUM_LEGACY_STATUSES = Sets.immutableEnumSet(InetnumStatus.LEGACY); private static final ImmutableSet INET6NUM_ALLOCATION_STATUSES = Sets.immutableEnumSet(Inet6numStatus.ALLOCATED_BY_RIR); private static final ImmutableSet INET6NUM_ASSIGNMENT_STATUSES = Sets.immutableEnumSet(Inet6numStatus.ASSIGNED_PI, Inet6numStatus.ASSIGNED_ANYCAST); diff --git a/whois-endtoend/src/test/groovy/net/ripe/db/whois/spec/integration/InetnumIntegrationSpec.groovy b/whois-endtoend/src/test/groovy/net/ripe/db/whois/spec/integration/InetnumIntegrationSpec.groovy index 799c0fbb39..78a8dadab6 100644 --- a/whois-endtoend/src/test/groovy/net/ripe/db/whois/spec/integration/InetnumIntegrationSpec.groovy +++ b/whois-endtoend/src/test/groovy/net/ripe/db/whois/spec/integration/InetnumIntegrationSpec.groovy @@ -1,6 +1,5 @@ package net.ripe.db.whois.spec.integration - import net.ripe.db.whois.spec.domain.SyncUpdate import org.junit.jupiter.api.Tag @@ -373,6 +372,251 @@ class InetnumIntegrationSpec extends BaseWhoisSourceSpec { response =~ /\*\*\*Info: Value 192.0.0.0\/24 converted to 192.0.0.0 - 192.0.0.255/ } + def "create ALLOCATED ASSIGNED PA inetnum using RS credentials"() { + when: + def response = syncUpdate(new SyncUpdate(data: """\ + inetnum: 192.0.0.0/24 + netname: RIPE-NCC + descr: description + country: DK + admin-c: TEST-PN + tech-c: TEST-PN + status: ALLOCATED-ASSIGNED PA + mnt-by: RIPE-NCC-HM-MNT + mnt-by: TEST-MNT + org: ORG-TOL5-TEST + source: TEST + password: update + password: hm + """.stripIndent(true))) + then: + response =~ /Create SUCCEEDED: \[inetnum\] 192.0.0.0 - 192.0.0.255/ + response =~ /\*\*\*Info: Value 192.0.0.0\/24 converted to 192.0.0.0 - 192.0.0.255/ + } + + def "create ALLOCATED ASSIGNED PA inetnum using override credentials"() { + when: + def response = syncUpdate(new SyncUpdate(data: """\ + inetnum: 192.0.0.0/24 + netname: RIPE-NCC + descr: description + country: DK + admin-c: TEST-PN + tech-c: TEST-PN + status: ALLOCATED-ASSIGNED PA + mnt-by: RIPE-NCC-HM-MNT + mnt-by: TEST-MNT + org: ORG-TOL5-TEST + source: TEST + override:denis,override1 + """.stripIndent(true))) + then: + response =~ /Create SUCCEEDED: \[inetnum\] 192.0.0.0 - 192.0.0.255/ + response =~ /\*\*\*Info: Value 192.0.0.0\/24 converted to 192.0.0.0 - 192.0.0.255/ + } + + def "create ALLOCATED ASSIGNED PA inetnum failed without RS maintainer"() { + when: + def response = syncUpdate(new SyncUpdate(data: """\ + inetnum: 192.0.0.0/24 + netname: RIPE-NCC + descr: description + country: DK + admin-c: TEST-PN + tech-c: TEST-PN + status: ALLOCATED-ASSIGNED PA + mnt-by: TEST-MNT + org: ORG-TOL5-TEST + source: TEST + password: update + password: hm + """.stripIndent(true))) + then: + response =~ /Create FAILED: \[inetnum\] 192.0.0.0 - 192.0.0.255/ + response.contains("***Error: Status ALLOCATED-ASSIGNED PA can only be created by the database\n" + + " administrator") + } + + def "create ALLOCATED ASSIGNED PA inetnum failed using user credentials"() { + when: + def response = syncUpdate(new SyncUpdate(data: """\ + inetnum: 192.0.0.0/24 + netname: RIPE-NCC + descr: description + country: DK + admin-c: TEST-PN + tech-c: TEST-PN + status: ALLOCATED-ASSIGNED PA + mnt-by: RIPE-NCC-HM-MNT + mnt-by: TEST-MNT + org: ORG-TOL5-TEST + source: TEST + password: update + """.stripIndent(true))) + then: + response =~ /Create FAILED: \[inetnum\] 192.0.0.0 - 192.0.0.255/ + response.contains("***Error: Setting status ALLOCATED-ASSIGNED PA requires administrative\n" + + " authorisation") + } + + def "modify status ALLOCATED ASSIGNED PA status to ALLOCATED PA by using user credentials"() { + given: + def insertResponse = syncUpdate(new SyncUpdate(data: """\ + inetnum: 192.0.0.0/24 + netname: RIPE-NCC + descr: description + country: DK + admin-c: TEST-PN + tech-c: TEST-PN + status: ALLOCATED-ASSIGNED PA + mnt-by: RIPE-NCC-HM-MNT + mnt-by: TEST-MNT + org: ORG-TOL5-TEST + source: TEST + password: hm + password: update + """.stripIndent(true))) + when: + insertResponse =~ /SUCCESS/ + then: + def response = syncUpdate new SyncUpdate(data: """\ + inetnum: 192.0.0.0/24 + netname: RIPE-NCC + descr: description + country: DK + admin-c: TEST-PN + tech-c: TEST-PN + status: ALLOCATED PA + mnt-by: RIPE-NCC-HM-MNT + mnt-by: TEST-MNT + org: ORG-TOL5-TEST + source: TEST + password: update + """.stripIndent(true)) + then: + response =~ /SUCCESS/ + response =~ /Modify SUCCEEDED: \[inetnum\] 192.0.0.0 - 192.0.0.255/ + } + + def "modify status ALLOCATED PA to ALLOCATED-ASSIGNED PA status by using user credentials"() { + given: + def insertResponse = syncUpdate(new SyncUpdate(data: """\ + inetnum: 192.0.0.0/24 + netname: RIPE-NCC + descr: description + country: DK + admin-c: TEST-PN + tech-c: TEST-PN + status: ALLOCATED PA + mnt-by: RIPE-NCC-HM-MNT + mnt-by: TEST-MNT + org: ORG-TOL5-TEST + source: TEST + password: hm + password: update + """.stripIndent(true))) + when: + insertResponse =~ /SUCCESS/ + then: + def response = syncUpdate new SyncUpdate(data: """\ + inetnum: 192.0.0.0/24 + netname: RIPE-NCC + descr: description + country: DK + admin-c: TEST-PN + tech-c: TEST-PN + status: ALLOCATED-ASSIGNED PA + mnt-by: RIPE-NCC-HM-MNT + mnt-by: TEST-MNT + org: ORG-TOL5-TEST + source: TEST + password: update + """.stripIndent(true)) + then: + response =~ /SUCCESS/ + response =~ /Modify SUCCEEDED: \[inetnum\] 192.0.0.0 - 192.0.0.255/ + } + + def "modify status ALLOCATED PA to ALLOCATED UNSPECIFIED status fails"() { + given: + def insertResponse = syncUpdate(new SyncUpdate(data: """\ + inetnum: 192.0.0.0/24 + netname: RIPE-NCC + descr: description + country: DK + admin-c: TEST-PN + tech-c: TEST-PN + status: ALLOCATED PA + mnt-by: RIPE-NCC-HM-MNT + mnt-by: TEST-MNT + org: ORG-TOL5-TEST + source: TEST + password: hm + password: update + """.stripIndent(true))) + when: + insertResponse =~ /SUCCESS/ + then: + def response = syncUpdate new SyncUpdate(data: """\ + inetnum: 192.0.0.0/24 + netname: RIPE-NCC + descr: description + country: DK + admin-c: TEST-PN + tech-c: TEST-PN + status: ALLOCATED UNSPECIFIED + mnt-by: RIPE-NCC-HM-MNT + mnt-by: TEST-MNT + org: ORG-TOL5-TEST + source: TEST + password: update + """.stripIndent(true)) + then: + response =~ /Modify FAILED: \[inetnum\] 192.0.0.0 - 192.0.0.255/ + response =~ /\*\*\*Error: status value cannot be changed, you must delete and re-create the + object/ + } + + def "modify status ALLOCATED ASSIGNED PA status to ALLOCATED UNSPECIFIED fails"() { + given: + def insertResponse = syncUpdate(new SyncUpdate(data: """\ + inetnum: 192.0.0.0/24 + netname: RIPE-NCC + descr: description + country: DK + admin-c: TEST-PN + tech-c: TEST-PN + status: ALLOCATED-ASSIGNED PA + mnt-by: RIPE-NCC-HM-MNT + mnt-by: TEST-MNT + org: ORG-TOL5-TEST + source: TEST + password: hm + password: update + """.stripIndent(true))) + when: + insertResponse =~ /SUCCESS/ + then: + def response = syncUpdate new SyncUpdate(data: """\ + inetnum: 192.0.0.0/24 + netname: RIPE-NCC + descr: description + country: DK + admin-c: TEST-PN + tech-c: TEST-PN + status: ALLOCATED UNSPECIFIED + mnt-by: RIPE-NCC-HM-MNT + mnt-by: TEST-MNT + org: ORG-TOL5-TEST + source: TEST + password: update + """.stripIndent(true)) + then: + response =~ /Modify FAILED: \[inetnum\] 192.0.0.0 - 192.0.0.255/ + response =~ /\*\*\*Error: status value cannot be changed, you must delete and re-create the + object/ + } + def "handle failure of out-of-range CIDR notation"() { when: def response = syncUpdate(new SyncUpdate(data: """\ @@ -432,7 +676,7 @@ class InetnumIntegrationSpec extends BaseWhoisSourceSpec { } def "create status ALLOCATED PA no alloc maintainer"() { - when: + when: def insertResponse = syncUpdate(new SyncUpdate(data: """\ inetnum: 192.0.0.0 - 192.0.0.255 netname: RIPE-NCC @@ -506,7 +750,7 @@ class InetnumIntegrationSpec extends BaseWhoisSourceSpec { then: response =~ /FAIL/ response =~ /Referenced organisation has wrong "org-type"/ - response =~ /Allowed values are \[IANA, RIR, LIR\]/ + response =~ /Allowed values are \[RIR, LIR\]/ } def "modify status ALLOCATED PA has reference to non-RIR organisation with override"() { diff --git a/whois-endtoend/src/test/groovy/net/ripe/db/whois/spec/update/InetnumSpec.groovy b/whois-endtoend/src/test/groovy/net/ripe/db/whois/spec/update/InetnumSpec.groovy index f0bb689625..212ff41572 100644 --- a/whois-endtoend/src/test/groovy/net/ripe/db/whois/spec/update/InetnumSpec.groovy +++ b/whois-endtoend/src/test/groovy/net/ripe/db/whois/spec/update/InetnumSpec.groovy @@ -123,6 +123,19 @@ class InetnumSpec extends BaseQueryUpdateSpec { mnt-lower: LIR-MNT source: TEST """, + "ALLOC-ASSIGN-PA": """\ + inetnum: 192.168.0.0 - 192.169.255.255 + netname: TEST-NET-NAME + descr: TEST network + country: NL + org: ORG-LIR1-TEST + admin-c: TP1-TEST + tech-c: TP1-TEST + status: ALLOCATED-ASSIGNED PA + mnt-by: RIPE-NCC-HM-MNT + mnt-lower: LIR-MNT + source: TEST + """, "P-NO-LOW": """\ inetnum: 192.168.128.0 - 192.168.255.255 netname: TEST-NET-NAME @@ -5046,6 +5059,119 @@ class InetnumSpec extends BaseQueryUpdateSpec { queryObjectNotFound("-rGBT inetnum 192.168.0.0 - 192.169.255.255", "inetnum", "192.168.0.0 - 192.169.255.255") } + def "delete ALLOCATED-ASSIGNED PA, override"() { + given: + syncUpdate(getTransient("ALLOC-ASSIGN-PA") + "override: denis,override1") + queryObject("-r -T inetnum 192.168.0.0 - 192.169.255.255", "inetnum", "192.168.0.0 - 192.169.255.255") + + when: + def message = syncUpdate("""\ + inetnum: 192.168.0.0 - 192.169.255.255 + netname: TEST-NET-NAME + descr: TEST network + country: NL + org: ORG-LIR1-TEST + admin-c: TP1-TEST + tech-c: TP1-TEST + status: ALLOCATED-ASSIGNED PA + mnt-by: RIPE-NCC-HM-MNT + mnt-lower: LIR-MNT + source: TEST + delete: test override + override: denis,override1 + + """.stripIndent(true) + ) + + then: + def ack = new AckResponse("", message) + + ack.summary.nrFound == 1 + ack.summary.assertSuccess(1, 0, 0, 1, 0) + ack.summary.assertErrors(0, 0, 0, 0) + + ack.countErrorWarnInfo(0, 1, 1) + ack.successes.any { it.operation == "Delete" && it.key == "[inetnum] 192.168.0.0 - 192.169.255.255" } + ack.infoSuccessMessagesFor("Delete", "[inetnum] 192.168.0.0 - 192.169.255.255") == [ + "Authorisation override used"] + + queryObjectNotFound("-rGBT inetnum 192.168.0.0 - 192.169.255.255", "inetnum", "192.168.0.0 - 192.169.255.255") + } + + def "delete ALLOCATED-ASSIGNED PA, rs"() { + given: + syncUpdate(getTransient("ALLOC-ASSIGN-PA") + "override: denis,override1") + queryObject("-r -T inetnum 192.168.0.0 - 192.169.255.255", "inetnum", "192.168.0.0 - 192.169.255.255") + + when: + def message = syncUpdate("""\ + inetnum: 192.168.0.0 - 192.169.255.255 + netname: TEST-NET-NAME + descr: TEST network + country: NL + org: ORG-LIR1-TEST + admin-c: TP1-TEST + tech-c: TP1-TEST + status: ALLOCATED-ASSIGNED PA + mnt-by: RIPE-NCC-HM-MNT + mnt-lower: LIR-MNT + source: TEST + delete: test rs + password: hm + + """.stripIndent(true) + ) + + then: + def ack = new AckResponse("", message) + + ack.summary.nrFound == 1 + ack.summary.assertSuccess(1, 0, 0, 1, 0) + ack.summary.assertErrors(0, 0, 0, 0) + + ack.successes.any { it.operation == "Delete" && it.key == "[inetnum] 192.168.0.0 - 192.169.255.255" } + + queryObjectNotFound("-rGBT inetnum 192.168.0.0 - 192.169.255.255", "inetnum", "192.168.0.0 - 192.169.255.255") + } + + def "delete ALLOCATED-ASSIGNED PA, user"() { + given: + syncUpdate(getTransient("ALLOC-ASSIGN-PA") + "override: denis,override1") + queryObject("-r -T inetnum 192.168.0.0 - 192.169.255.255", "inetnum", "192.168.0.0 - 192.169.255.255") + + when: + def message = syncUpdate("""\ + inetnum: 192.168.0.0 - 192.169.255.255 + netname: TEST-NET-NAME + descr: TEST network + country: NL + org: ORG-LIR1-TEST + admin-c: TP1-TEST + tech-c: TP1-TEST + status: ALLOCATED-ASSIGNED PA + mnt-by: RIPE-NCC-HM-MNT + mnt-lower: LIR-MNT + source: TEST + delete: test user + password: lir + + """.stripIndent(true) + ) + + then: + def ack = new AckResponse("", message) + + ack.summary.nrFound == 1 + ack.summary.assertSuccess(0, 0, 0, 0, 0) + ack.summary.assertErrors(1, 0, 0, 1) + + ack.countErrorWarnInfo(1, 0, 0) + ack.errors.any { it.operation == "Delete" && it.key == "[inetnum] 192.168.0.0 - 192.169.255.255" } + ack.errorMessagesFor("Delete", "[inetnum] 192.168.0.0 - 192.169.255.255") == + ["Deleting this object requires administrative authorisation"] + + } + def "modify assignment, joint RS & user mnt-by, remove RS mntner, user auth"() { given: syncUpdate(getTransient("ALLOC-PA") + "password: hm\npassword: owner3") diff --git a/whois-endtoend/src/test/groovy/net/ripe/db/whois/spec/update/InetnumStatusChildSpec.groovy b/whois-endtoend/src/test/groovy/net/ripe/db/whois/spec/update/InetnumStatusChildSpec.groovy index dd3308d848..cafa30896a 100644 --- a/whois-endtoend/src/test/groovy/net/ripe/db/whois/spec/update/InetnumStatusChildSpec.groovy +++ b/whois-endtoend/src/test/groovy/net/ripe/db/whois/spec/update/InetnumStatusChildSpec.groovy @@ -4,6 +4,7 @@ package net.ripe.db.whois.spec.update import net.ripe.db.whois.spec.BaseQueryUpdateSpec import net.ripe.db.whois.spec.domain.AckResponse import net.ripe.db.whois.spec.domain.Message +import net.ripe.db.whois.spec.domain.SyncUpdate import spock.lang.Ignore @org.junit.jupiter.api.Tag("IntegrationTest") @@ -39,6 +40,20 @@ class InetnumStatusChildSpec extends BaseQueryUpdateSpec { mnt-lower: LIR2-MNT source: TEST """, + "ALLOC-ASSIGN-PA": """\ + inetnum: 192.168.0.0 - 192.169.255.255 + netname: TEST-NET-NAME + descr: TEST network + country: NL + org: ORG-LIR1-TEST + admin-c: TP1-TEST + tech-c: TP1-TEST + status: ALLOCATED-ASSIGNED PA + mnt-by: RIPE-NCC-HM-MNT + mnt-lower: LIR-MNT + mnt-lower: LIR2-MNT + source: TEST + """, "ALLOC-PA2": """\ inetnum: 192.170.0.0 - 192.170.255.255 netname: TEST-NET-NAME @@ -375,6 +390,119 @@ class InetnumStatusChildSpec extends BaseQueryUpdateSpec { queryObjectNotFound("-rGBT inetnum 192.168.0.0 - 192.168.255.255", "inetnum", "192.168.0.0 - 192.168.255.255") } + // Tests for parent ALLOCATED-ASSIGNED PA + def "create child LIR-PARTITIONED PA, parent status ALLOCATED-ASSIGNED PA"() { + given: + def insertResponse = syncUpdate(new SyncUpdate(data: """\ + inetnum: 192.168.0.0 - 192.169.255.255 + netname: TEST-NET-NAME + descr: TEST network + country: NL + org: ORG-LIR1-TEST + admin-c: TP1-TEST + tech-c: TP1-TEST + status: ALLOCATED-ASSIGNED PA + mnt-by: RIPE-NCC-HM-MNT + mnt-lower: LIR-MNT + mnt-lower: LIR2-MNT + source: TEST + password: hm + password: owner3 + password: lir + """.stripIndent(true))) + when: + insertResponse =~ /SUCCESS/ + + then: + def ack = syncUpdateWithResponse("""\ + inetnum: 192.168.0.0 - 192.168.255.255 + netname: TEST-NET-NAME + descr: TEST network + country: NL + org: ORG-LIR1-TEST + admin-c: TP1-TEST + tech-c: TP1-TEST + status: LIR-PARTITIONED PA + mnt-by: RIPE-NCC-HM-MNT + mnt-lower: LIR-MNT + source: TEST + + password: hm + password: owner3 + password: lir + """.stripIndent(true) + ) + + then: + + ack.summary.nrFound == 1 + ack.summary.assertSuccess(0, 0, 0, 0, 0) + ack.summary.assertErrors(1, 1, 0, 0) + + ack.countErrorWarnInfo(1, 0, 0) + ack.errors.any { it.operation == "Create" && it.key == "[inetnum] 192.168.0.0 - 192.168.255.255" } + ack.errorMessagesFor("Create", "[inetnum] 192.168.0.0 - 192.168.255.255") == + ["inetnum parent has incorrect status: ALLOCATED-ASSIGNED PA"] + + queryObjectNotFound("-rGBT inetnum 192.168.0.0 - 192.168.255.255", "inetnum", "192.168.0.0 - 192.168.255.255") + } + + def "create child SUB-ALLOCATED PA, parent status ALLOCATED-ASSIGNED PA"() { + given: + def insertResponse = syncUpdate(new SyncUpdate(data: """\ + inetnum: 192.168.0.0 - 192.169.255.255 + netname: TEST-NET-NAME + descr: TEST network + country: NL + org: ORG-LIR1-TEST + admin-c: TP1-TEST + tech-c: TP1-TEST + status: ALLOCATED-ASSIGNED PA + mnt-by: RIPE-NCC-HM-MNT + mnt-lower: LIR-MNT + mnt-lower: LIR2-MNT + source: TEST + password: hm + password: owner3 + password: lir + """.stripIndent(true))) + when: + insertResponse =~ /SUCCESS/ + + then: + def ack = syncUpdateWithResponse("""\ + inetnum: 192.168.0.0 - 192.168.255.255 + netname: TEST-NET-NAME + descr: TEST network + country: NL + org: ORG-LIR1-TEST + admin-c: TP1-TEST + tech-c: TP1-TEST + status: SUB-ALLOCATED PA + mnt-by: RIPE-NCC-HM-MNT + mnt-lower: LIR-MNT + source: TEST + + password: hm + password: owner3 + password: lir + """.stripIndent(true) + ) + + then: + + ack.summary.nrFound == 1 + ack.summary.assertSuccess(0, 0, 0, 0, 0) + ack.summary.assertErrors(1, 1, 0, 0) + + ack.countErrorWarnInfo(1, 0, 0) + ack.errors.any { it.operation == "Create" && it.key == "[inetnum] 192.168.0.0 - 192.168.255.255" } + ack.errorMessagesFor("Create", "[inetnum] 192.168.0.0 - 192.168.255.255") == + ["inetnum parent has incorrect status: ALLOCATED-ASSIGNED PA"] + + queryObjectNotFound("-rGBT inetnum 192.168.0.0 - 192.168.255.255", "inetnum", "192.168.0.0 - 192.168.255.255") + } + def "create child ALLOCATED UNSPECIFIED, parent status LIR-PARTITIONED PA"() { given: syncUpdate(getTransient("ALLOC-PA") + "password: owner3\npassword: hm") @@ -790,6 +918,131 @@ class InetnumStatusChildSpec extends BaseQueryUpdateSpec { queryObject("-rGBT inetnum 192.168.0.0 - 192.169.255.255", "inetnum", "192.168.0.0 - 192.169.255.255") } + // Create child object with status ALLOCATED-ASSIGNED PA tests + + def "create child ALLOCATED-ASSIGNED PA, parent status ALLOCATED UNSPECIFIED"() { + given: + syncUpdate(getTransient("ALLOC-UNS") + "password: owner3\npassword: hm") + queryObject("-r -T inetnum 192.0.0.0 - 192.255.255.255", "inetnum", "192.0.0.0 - 192.255.255.255") + + expect: + queryObjectNotFound("-r -T inetnum 192.168.0.0 - 192.169.255.255", "inetnum", "192.168.0.0 - 192.169.255.255") + + when: + def ack = syncUpdateWithResponse("""\ + inetnum: 192.168.0.0 - 192.169.255.255 + netname: TEST-NET-NAME + descr: TEST network + country: NL + org: ORG-LIR1-TEST + admin-c: TP1-TEST + tech-c: TP1-TEST + status: ALLOCATED-ASSIGNED PA + mnt-by: RIPE-NCC-HM-MNT + mnt-lower: LIR-MNT + source: TEST + + password: hm + password: owner3 + """.stripIndent(true) + ) + + then: + + ack.summary.nrFound == 1 + ack.summary.assertSuccess(1, 1, 0, 0, 0) + ack.summary.assertErrors(0, 0, 0, 0) + + ack.countErrorWarnInfo(0, 0, 0) + ack.successes.any { it.operation == "Create" && it.key == "[inetnum] 192.168.0.0 - 192.169.255.255" } + + queryObject("-rGBT inetnum 192.168.0.0 - 192.169.255.255", "inetnum", "192.168.0.0 - 192.169.255.255") + } + + def "create child ALLOCATED-ASSIGNED PA, parent status ALLOCATED PA"() { + given: + syncUpdate(getTransient("ALLOC-PA") + "password: owner3\npassword: hm") + queryObject("-r -T inetnum 192.168.0.0 - 192.169.255.255", "inetnum", "192.168.0.0 - 192.169.255.255") + + expect: + queryObjectNotFound("-r -T inetnum 192.168.0.0 - 192.168.255.255", "inetnum", "192.168.0.0 - 192.168.255.255") + + when: + def ack = syncUpdateWithResponse("""\ + inetnum: 192.168.0.0 - 192.168.255.255 + netname: TEST-NET-NAME + descr: TEST network + country: NL + org: ORG-LIR1-TEST + admin-c: TP1-TEST + tech-c: TP1-TEST + status: ALLOCATED PA + mnt-by: RIPE-NCC-HM-MNT + mnt-lower: LIR-MNT + source: TEST + + password: hm + password: owner3 + password: lir + """.stripIndent(true) + ) + + then: + + ack.summary.nrFound == 1 + ack.summary.assertSuccess(0, 0, 0, 0, 0) + ack.summary.assertErrors(1, 1, 0, 0) + + ack.countErrorWarnInfo(1, 0, 0) + ack.errors.any { it.operation == "Create" && it.key == "[inetnum] 192.168.0.0 - 192.168.255.255" } + ack.errorMessagesFor("Create", "[inetnum] 192.168.0.0 - 192.168.255.255") == + ["inetnum parent has incorrect status: ALLOCATED PA"] + + queryObjectNotFound("-rGBT inetnum 192.168.0.0 - 192.168.255.255", "inetnum", "192.168.0.0 - 192.168.255.255") + } + + def "create child ASSIGNED PA, parent status ALLOCATED ASSIGNED PA"() { + given: + syncUpdate(getTransient("ALLOC-ASSIGN-PA") + "password: owner3\npassword: hm") + queryObject("-r -T inetnum 192.168.0.0 - 192.169.255.255", "inetnum", "192.168.0.0 - 192.169.255.255") + + expect: + queryObjectNotFound("-r -T inetnum 192.168.0.0 - 192.168.255.255", "inetnum", "192.168.0.0 - 192.168.255.255") + + when: + def ack = syncUpdateWithResponse("""\ + inetnum: 192.168.0.0 - 192.168.255.255 + netname: TEST-NET-NAME + descr: TEST network + country: NL + org: ORG-LIR1-TEST + admin-c: TP1-TEST + tech-c: TP1-TEST + status: ASSIGNED PA + mnt-by: RIPE-NCC-HM-MNT + mnt-lower: LIR-MNT + source: TEST + + password: hm + password: owner3 + password: lir + """.stripIndent(true) + ) + + then: + + ack.summary.nrFound == 1 + ack.summary.assertSuccess(0, 0, 0, 0, 0) + ack.summary.assertErrors(1, 1, 0, 0) + + ack.countErrorWarnInfo(1, 0, 0) + ack.errors.any { it.operation == "Create" && it.key == "[inetnum] 192.168.0.0 - 192.168.255.255" } + ack.errorMessagesFor("Create", "[inetnum] 192.168.0.0 - 192.168.255.255") == + ["inetnum parent has incorrect status: ALLOCATED-ASSIGNED PA"] + + queryObjectNotFound("-rGBT inetnum 192.168.0.0 - 192.168.255.255", "inetnum", "192.168.0.0 - 192.168.255.255") + } + // Create child object with status ALLOCATED PA tests def "create child ALLOCATED PA, parent status ALLOCATED UNSPECIFIED"() { @@ -1203,7 +1456,7 @@ class InetnumStatusChildSpec extends BaseQueryUpdateSpec { ack.countErrorWarnInfo(1, 0, 0) ack.errors.any { it.operation == "Create" && it.key == "[inetnum] 192.168.0.0 - 192.169.255.255" } ack.errorMessagesFor("Create", "[inetnum] 192.168.0.0 - 192.169.255.255") == - ["Referenced organisation has wrong \"org-type\". Allowed values are [IANA, RIR, LIR]"] + ["Referenced organisation has wrong \"org-type\". Allowed values are [RIR, LIR]"] queryObjectNotFound("-rGBT inetnum 192.168.0.0 - 192.169.255.255", "inetnum", "192.168.0.0 - 192.169.255.255") } @@ -1283,7 +1536,7 @@ class InetnumStatusChildSpec extends BaseQueryUpdateSpec { ack.countErrorWarnInfo(1, 0, 0) ack.errors.any { it.operation == "Create" && it.key == "[inetnum] 192.168.0.0 - 192.169.255.255" } ack.errorMessagesFor("Create", "[inetnum] 192.168.0.0 - 192.169.255.255") == - ["Referenced organisation has wrong \"org-type\". Allowed values are [IANA, RIR, LIR]"] + ["Referenced organisation has wrong \"org-type\". Allowed values are [RIR, LIR]"] queryObjectNotFound("-rGBT inetnum 192.168.0.0 - 192.169.255.255", "inetnum", "192.168.0.0 - 192.169.255.255") } @@ -1318,13 +1571,15 @@ class InetnumStatusChildSpec extends BaseQueryUpdateSpec { then: ack.summary.nrFound == 1 - ack.summary.assertSuccess(1, 1, 0, 0, 0) - ack.summary.assertErrors(0, 0, 0, 0) + ack.summary.assertSuccess(0, 0, 0, 0, 0) + ack.summary.assertErrors(1, 1, 0, 0) - ack.countErrorWarnInfo(0, 0, 0) - ack.successes.any { it.operation == "Create" && it.key == "[inetnum] 192.168.0.0 - 192.169.255.255" } + ack.countErrorWarnInfo(1, 0, 0) + ack.errors.any { it.operation == "Create" && it.key == "[inetnum] 192.168.0.0 - 192.169.255.255" } + ack.errorMessagesFor("Create", "[inetnum] 192.168.0.0 - 192.169.255.255") == + ["Referenced organisation has wrong \"org-type\". Allowed values are [RIR, LIR]"] - queryObject("-rGBT inetnum 192.168.0.0 - 192.169.255.255", "inetnum", "192.168.0.0 - 192.169.255.255") + queryObjectNotFound("-rGBT inetnum 192.168.0.0 - 192.169.255.255", "inetnum", "192.168.0.0 - 192.169.255.255") } // Create child object with status SUB-ALLOCATED PA tests diff --git a/whois-query/src/test/java/net/ripe/db/whois/query/integration/TemplateTestIntegration.java b/whois-query/src/test/java/net/ripe/db/whois/query/integration/TemplateTestIntegration.java index 2d1ad3f867..46b5978add 100644 --- a/whois-query/src/test/java/net/ripe/db/whois/query/integration/TemplateTestIntegration.java +++ b/whois-query/src/test/java/net/ripe/db/whois/query/integration/TemplateTestIntegration.java @@ -262,6 +262,7 @@ public void verbose_description() { " o ALLOCATED UNSPECIFIED\n" + " o LIR-PARTITIONED PA\n" + " o SUB-ALLOCATED PA\n" + + " o ALLOCATED-ASSIGNED PA\n" + " o ASSIGNED PA\n" + " o ASSIGNED PI\n" + " o ASSIGNED ANYCAST\n" + diff --git a/whois-rpsl/src/main/java/net/ripe/db/whois/common/rpsl/attrs/InetnumStatus.java b/whois-rpsl/src/main/java/net/ripe/db/whois/common/rpsl/attrs/InetnumStatus.java index 394062d2a4..5feef639d4 100644 --- a/whois-rpsl/src/main/java/net/ripe/db/whois/common/rpsl/attrs/InetnumStatus.java +++ b/whois-rpsl/src/main/java/net/ripe/db/whois/common/rpsl/attrs/InetnumStatus.java @@ -18,17 +18,18 @@ public enum InetnumStatus implements InetStatus { - ALLOCATED_PA("ALLOCATED PA", IANA, RIR, LIR), + ALLOCATED_PA("ALLOCATED PA", RIR, LIR), ALLOCATED_UNSPECIFIED("ALLOCATED UNSPECIFIED", IANA, RIR, LIR), LIR_PARTITIONED_PA("LIR-PARTITIONED PA", LIR, OTHER), SUB_ALLOCATED_PA("SUB-ALLOCATED PA", LIR, OTHER), + ALLOCATED_ASSIGNED_PA("ALLOCATED-ASSIGNED PA", RIR, LIR), ASSIGNED_PA("ASSIGNED PA", LIR, OTHER), ASSIGNED_PI("ASSIGNED PI", LIR, OTHER, RIR), ASSIGNED_ANYCAST("ASSIGNED ANYCAST", LIR, OTHER), LEGACY("LEGACY", LIR, OTHER); - private static final EnumSet RS_MNTNER_STATUSES = EnumSet.of(ALLOCATED_PA, ASSIGNED_ANYCAST, ALLOCATED_UNSPECIFIED); - private static final EnumSet NEEDS_ORG_REFERENCE = EnumSet.of(ALLOCATED_PA, ALLOCATED_UNSPECIFIED); + private static final EnumSet RS_MNTNER_STATUSES = EnumSet.of(ALLOCATED_PA, ALLOCATED_ASSIGNED_PA, ASSIGNED_ANYCAST, ALLOCATED_UNSPECIFIED); + private static final EnumSet NEEDS_ORG_REFERENCE = EnumSet.of(ALLOCATED_PA, ALLOCATED_ASSIGNED_PA, ALLOCATED_UNSPECIFIED); private static final EnumSet NEEDS_PARENT_RS_MNTR = EnumSet.of(ALLOCATED_UNSPECIFIED); private static final EnumMap> PARENT_STATUS; @@ -36,6 +37,7 @@ public enum InetnumStatus implements InetStatus { static { PARENT_STATUS = Maps.newEnumMap(InetnumStatus.class); PARENT_STATUS.put(ALLOCATED_PA, EnumSet.of(ALLOCATED_UNSPECIFIED)); + PARENT_STATUS.put(ALLOCATED_ASSIGNED_PA, EnumSet.of(ALLOCATED_UNSPECIFIED)); PARENT_STATUS.put(ALLOCATED_UNSPECIFIED, EnumSet.of(ALLOCATED_UNSPECIFIED)); PARENT_STATUS.put(LIR_PARTITIONED_PA, EnumSet.of(ALLOCATED_UNSPECIFIED, ALLOCATED_PA, LIR_PARTITIONED_PA, SUB_ALLOCATED_PA)); PARENT_STATUS.put(SUB_ALLOCATED_PA, EnumSet.of(ALLOCATED_PA, LIR_PARTITIONED_PA, SUB_ALLOCATED_PA)); diff --git a/whois-rpsl/src/test/java/net/ripe/db/whois/common/rpsl/attrs/InetnumStatusTest.java b/whois-rpsl/src/test/java/net/ripe/db/whois/common/rpsl/attrs/InetnumStatusTest.java index d6f598fa94..6824e5cdc9 100644 --- a/whois-rpsl/src/test/java/net/ripe/db/whois/common/rpsl/attrs/InetnumStatusTest.java +++ b/whois-rpsl/src/test/java/net/ripe/db/whois/common/rpsl/attrs/InetnumStatusTest.java @@ -53,7 +53,7 @@ public void parentVerification() { public void allowedOrgTypesChecks() { assertThat(ALLOCATED_PA.isValidOrgType(OrgType.LIR), is(true)); assertThat(ALLOCATED_PA.isValidOrgType(OrgType.RIR), is(true)); - assertThat(ALLOCATED_PA.isValidOrgType(OrgType.IANA), is(true)); + assertThat(ALLOCATED_PA.isValidOrgType(OrgType.IANA), is(false)); assertThat(ALLOCATED_PA.isValidOrgType(OrgType.DIRECT_ASSIGNMENT), is(false)); assertThat(ALLOCATED_UNSPECIFIED.isValidOrgType(OrgType.LIR), is(true)); diff --git a/whois-update/src/main/java/net/ripe/db/whois/update/handler/validator/inetnum/InetnumStatusValidator.java b/whois-update/src/main/java/net/ripe/db/whois/update/handler/validator/inetnum/InetnumStatusValidator.java index 26fbe78e76..ddb4ed444b 100644 --- a/whois-update/src/main/java/net/ripe/db/whois/update/handler/validator/inetnum/InetnumStatusValidator.java +++ b/whois-update/src/main/java/net/ripe/db/whois/update/handler/validator/inetnum/InetnumStatusValidator.java @@ -70,10 +70,14 @@ private List validateModify(final PreparedUpdate update, final UpdateCo final List validationMessages = Lists.newArrayList(); - final CIString originalStatus = update.getReferenceObject().getValueForAttribute(AttributeType.STATUS); - final CIString updateStatus = update.getUpdatedObject().getValueForAttribute(AttributeType.STATUS); + final InetnumStatus originalStatus = InetnumStatus.getStatusFor(update.getReferenceObject().getValueForAttribute(AttributeType.STATUS)); + final InetnumStatus updateStatus = InetnumStatus.getStatusFor(update.getUpdatedObject().getValueForAttribute(AttributeType.STATUS)); - if (!Objects.equals(originalStatus, updateStatus)) { + if(canChangeStatus(originalStatus, updateStatus)) { + return Collections.EMPTY_LIST; + } + + if (originalStatus != updateStatus) { validationMessages.add(UpdateMessages.statusChange()); } @@ -82,6 +86,11 @@ private List validateModify(final PreparedUpdate update, final UpdateCo return validationMessages; } + public static final boolean canChangeStatus(final InetnumStatus originalStatus, final InetnumStatus updateStatus) { + return (originalStatus == InetnumStatus.ALLOCATED_PA && updateStatus == InetnumStatus.ALLOCATED_ASSIGNED_PA) + || (originalStatus == InetnumStatus.ALLOCATED_ASSIGNED_PA && updateStatus == InetnumStatus.ALLOCATED_PA); + } + private List validateDelete(final PreparedUpdate update, final UpdateContext updateContext) { if (update.getReferenceObject() == null || update.getUpdatedObject() == null) { return Collections.emptyList(); diff --git a/whois-update/src/main/java/net/ripe/db/whois/update/handler/validator/inetnum/InetnumStrictStatusValidator.java b/whois-update/src/main/java/net/ripe/db/whois/update/handler/validator/inetnum/InetnumStrictStatusValidator.java index ea2d37e7ae..2efc42650c 100644 --- a/whois-update/src/main/java/net/ripe/db/whois/update/handler/validator/inetnum/InetnumStrictStatusValidator.java +++ b/whois-update/src/main/java/net/ripe/db/whois/update/handler/validator/inetnum/InetnumStrictStatusValidator.java @@ -24,13 +24,14 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; -import javax.annotation.CheckForNull; +import java.util.Collections; import java.util.List; import java.util.Set; import static net.ripe.db.whois.common.rpsl.AttributeType.STATUS; import static net.ripe.db.whois.common.rpsl.attrs.InetnumStatus.LEGACY; import static net.ripe.db.whois.update.domain.Action.CREATE; +import static net.ripe.db.whois.update.domain.Action.MODIFY; /** * Apply stricter status validation when creating an inetnum object. @@ -38,7 +39,7 @@ @Component public class InetnumStrictStatusValidator implements BusinessRuleValidator { - private static final ImmutableList ACTIONS = ImmutableList.of(CREATE); + private static final ImmutableList ACTIONS = ImmutableList.of(CREATE, MODIFY); private static final ImmutableList TYPES = ImmutableList.of(ObjectType.INETNUM); private final RpslObjectDao objectDao; @@ -60,6 +61,10 @@ public InetnumStrictStatusValidator( @Override public List performValidation(final PreparedUpdate update, final UpdateContext updateContext) { + if(canSkipValidation(update)) { + return Collections.EMPTY_LIST; + } + return validateCreate(update, updateContext); } @@ -97,7 +102,7 @@ private void checkAuthorisationForStatus(final PreparedUpdate update, final Upda validationMessages.add(UpdateMessages.statusRequiresAuthorization(update.getUpdatedObject().getValueForAttribute(STATUS).toString())); return; } - if (!updateContext.getSubject(update).hasPrincipal(Principal.RS_MAINTAINER)) { + if (update.getAction() == CREATE && !updateContext.getSubject(update).hasPrincipal(Principal.RS_MAINTAINER)) { validationMessages.add(UpdateMessages.authorisationRequiredForSetStatus(currentStatus.toString())); } } @@ -111,6 +116,17 @@ private void validateLegacyStatus(final InetnumStatus currentStatus, final Inetn } } + private boolean canSkipValidation(final PreparedUpdate update) { + if(update.getAction() == CREATE) { + return false; + } + + final InetnumStatus originalStatus = InetnumStatus.getStatusFor(update.getReferenceObject().getValueForAttribute(AttributeType.STATUS)); + final InetnumStatus updateStatus = InetnumStatus.getStatusFor(update.getUpdatedObject().getValueForAttribute(AttributeType.STATUS)); + + return (originalStatus == updateStatus) || !InetnumStatusValidator.canChangeStatus(originalStatus, updateStatus); + } + @Override public boolean isSkipForOverride() { return true; diff --git a/whois-update/src/main/java/net/ripe/db/whois/update/handler/validator/inetnum/LirMntByValidator.java b/whois-update/src/main/java/net/ripe/db/whois/update/handler/validator/inetnum/LirMntByValidator.java index a1c5ce7cce..64435a4f6e 100644 --- a/whois-update/src/main/java/net/ripe/db/whois/update/handler/validator/inetnum/LirMntByValidator.java +++ b/whois-update/src/main/java/net/ripe/db/whois/update/handler/validator/inetnum/LirMntByValidator.java @@ -77,7 +77,9 @@ private boolean isAllocation(final RpslObject originalObject) { // TODO - To make it consistent, we can check for RIPE-NCC-HM-MNT return InetnumStatus.ALLOCATED_PA.equals(status) || - InetnumStatus.ALLOCATED_UNSPECIFIED.equals(status) || Inet6numStatus.ALLOCATED_BY_RIR.equals(status); + InetnumStatus.ALLOCATED_UNSPECIFIED.equals(status) || + InetnumStatus.ALLOCATED_ASSIGNED_PA.equals(status) || + Inet6numStatus.ALLOCATED_BY_RIR.equals(status); } @Override diff --git a/whois-update/src/test/java/net/ripe/db/whois/update/handler/validator/inetnum/InetnumStrictStatusValidatorTest.java b/whois-update/src/test/java/net/ripe/db/whois/update/handler/validator/inetnum/InetnumStrictStatusValidatorTest.java index 943bf7d3bc..426606d3e9 100644 --- a/whois-update/src/test/java/net/ripe/db/whois/update/handler/validator/inetnum/InetnumStrictStatusValidatorTest.java +++ b/whois-update/src/test/java/net/ripe/db/whois/update/handler/validator/inetnum/InetnumStrictStatusValidatorTest.java @@ -13,6 +13,7 @@ import net.ripe.db.whois.common.rpsl.RpslObject; import net.ripe.db.whois.update.authentication.Principal; import net.ripe.db.whois.update.authentication.Subject; +import net.ripe.db.whois.update.domain.Action; import net.ripe.db.whois.update.domain.PreparedUpdate; import net.ripe.db.whois.update.domain.UpdateContext; import net.ripe.db.whois.update.domain.UpdateMessages; @@ -48,6 +49,7 @@ public class InetnumStrictStatusValidatorTest { public void setup() { lenient().when(updateContext.getSubject(update)).thenReturn(authenticationSubject); lenient().when(maintainers.isRsMaintainer(ciSet("RIPE-NCC-HM-MNT"))).thenReturn(true); + lenient().when(update.getAction()).thenReturn(Action.CREATE); } @Test