Tests for cache info etc

This commit is contained in:
simonredfern 2026-01-01 03:22:28 +01:00
parent 4a20168da7
commit a366afaad4

View File

@ -0,0 +1,353 @@
/**
Open Bank Project - API
Copyright (C) 2011-2024, TESOBE GmbH
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
Email: contact@tesobe.com
TESOBE GmbH
Osloerstrasse 16/17
Berlin 13359, Germany
This product includes software developed at
TESOBE (http://www.tesobe.com/)
*/
package code.api.v6_0_0
import code.api.util.APIUtil.OAuth._
import code.api.util.ApiRole.{CanGetCacheConfig, CanGetCacheInfo, CanInvalidateCacheNamespace}
import code.api.util.ErrorMessages.{InvalidJsonFormat, UserHasMissingRoles, UserNotLoggedIn}
import code.api.v6_0_0.OBPAPI6_0_0.Implementations6_0_0
import code.entitlement.Entitlement
import com.github.dwickern.macros.NameOf.nameOf
import com.openbankproject.commons.model.ErrorMessage
import com.openbankproject.commons.util.ApiVersion
import net.liftweb.json.Serialization.write
import org.scalatest.Tag
class CacheEndpointsTest extends V600ServerSetup {
/**
* Test tags
* Example: To run tests with tag "getCacheConfig":
* mvn test -D tagsToInclude
*
* This is made possible by the scalatest maven plugin
*/
object VersionOfApi extends Tag(ApiVersion.v6_0_0.toString)
object ApiEndpoint1 extends Tag(nameOf(Implementations6_0_0.getCacheConfig))
object ApiEndpoint2 extends Tag(nameOf(Implementations6_0_0.getCacheInfo))
object ApiEndpoint3 extends Tag(nameOf(Implementations6_0_0.invalidateCacheNamespace))
// ============================================================================================================
// GET /system/cache/config - Get Cache Configuration
// ============================================================================================================
feature(s"test $ApiEndpoint1 version $VersionOfApi - Unauthorized access") {
scenario("We call getCacheConfig without user credentials", ApiEndpoint1, VersionOfApi) {
When("We make a request v6.0.0 without credentials")
val request = (v6_0_0_Request / "system" / "cache" / "config").GET
val response = makeGetRequest(request)
Then("We should get a 401")
response.code should equal(401)
response.body.extract[ErrorMessage].message should equal(UserNotLoggedIn)
}
}
feature(s"test $ApiEndpoint1 version $VersionOfApi - Missing role") {
scenario("We call getCacheConfig without the CanGetCacheConfig role", ApiEndpoint1, VersionOfApi) {
When("We make a request v6.0.0 without the required role")
val request = (v6_0_0_Request / "system" / "cache" / "config").GET <@ (user1)
val response = makeGetRequest(request)
Then("We should get a 403")
response.code should equal(403)
And("error should be " + UserHasMissingRoles + CanGetCacheConfig)
response.body.extract[ErrorMessage].message should equal(UserHasMissingRoles + CanGetCacheConfig)
}
}
feature(s"test $ApiEndpoint1 version $VersionOfApi - Authorized access") {
scenario("We call getCacheConfig with the CanGetCacheConfig role", ApiEndpoint1, VersionOfApi) {
Given("We have a user with CanGetCacheConfig entitlement")
Entitlement.entitlement.vend.addEntitlement("", resourceUser1.userId, CanGetCacheConfig.toString)
When("We make a request v6.0.0 with proper role")
val request = (v6_0_0_Request / "system" / "cache" / "config").GET <@ (user1)
val response = makeGetRequest(request)
Then("We should get a 200")
response.code should equal(200)
And("The response should have the correct structure")
val cacheConfig = response.body.extract[CacheConfigJsonV600]
cacheConfig.providers should not be empty
cacheConfig.instance_id should not be empty
cacheConfig.environment should not be empty
cacheConfig.global_prefix should not be empty
And("Providers should have valid data")
cacheConfig.providers.foreach { provider =>
provider.provider should not be empty
provider.enabled shouldBe a[Boolean]
}
}
}
// ============================================================================================================
// GET /system/cache/info - Get Cache Information
// ============================================================================================================
feature(s"test $ApiEndpoint2 version $VersionOfApi - Unauthorized access") {
scenario("We call getCacheInfo without user credentials", ApiEndpoint2, VersionOfApi) {
When("We make a request v6.0.0 without credentials")
val request = (v6_0_0_Request / "system" / "cache" / "info").GET
val response = makeGetRequest(request)
Then("We should get a 401")
response.code should equal(401)
response.body.extract[ErrorMessage].message should equal(UserNotLoggedIn)
}
}
feature(s"test $ApiEndpoint2 version $VersionOfApi - Missing role") {
scenario("We call getCacheInfo without the CanGetCacheInfo role", ApiEndpoint2, VersionOfApi) {
When("We make a request v6.0.0 without the required role")
val request = (v6_0_0_Request / "system" / "cache" / "info").GET <@ (user1)
val response = makeGetRequest(request)
Then("We should get a 403")
response.code should equal(403)
And("error should be " + UserHasMissingRoles + CanGetCacheInfo)
response.body.extract[ErrorMessage].message should equal(UserHasMissingRoles + CanGetCacheInfo)
}
}
feature(s"test $ApiEndpoint2 version $VersionOfApi - Authorized access") {
scenario("We call getCacheInfo with the CanGetCacheInfo role", ApiEndpoint2, VersionOfApi) {
Given("We have a user with CanGetCacheInfo entitlement")
Entitlement.entitlement.vend.addEntitlement("", resourceUser1.userId, CanGetCacheInfo.toString)
When("We make a request v6.0.0 with proper role")
val request = (v6_0_0_Request / "system" / "cache" / "info").GET <@ (user1)
val response = makeGetRequest(request)
Then("We should get a 200")
response.code should equal(200)
And("The response should have the correct structure")
val cacheInfo = response.body.extract[CacheInfoJsonV600]
cacheInfo.namespaces should not be null
cacheInfo.total_keys should be >= 0
cacheInfo.redis_available shouldBe a[Boolean]
And("Each namespace should have valid data")
cacheInfo.namespaces.foreach { namespace =>
namespace.namespace_id should not be empty
namespace.prefix should not be empty
namespace.current_version should be > 0L
namespace.key_count should be >= 0
namespace.description should not be empty
namespace.category should not be empty
}
}
}
// ============================================================================================================
// POST /management/cache/namespaces/invalidate - Invalidate Cache Namespace
// ============================================================================================================
feature(s"test $ApiEndpoint3 version $VersionOfApi - Unauthorized access") {
scenario("We call invalidateCacheNamespace without user credentials", ApiEndpoint3, VersionOfApi) {
When("We make a request v6.0.0 without credentials")
val request = (v6_0_0_Request / "management" / "cache" / "namespaces" / "invalidate").POST
val response = makePostRequest(request, write(InvalidateCacheNamespaceJsonV600("rd_localised")))
Then("We should get a 401")
response.code should equal(401)
response.body.extract[ErrorMessage].message should equal(UserNotLoggedIn)
}
}
feature(s"test $ApiEndpoint3 version $VersionOfApi - Missing role") {
scenario("We call invalidateCacheNamespace without the CanInvalidateCacheNamespace role", ApiEndpoint3, VersionOfApi) {
When("We make a request v6.0.0 without the required role")
val request = (v6_0_0_Request / "management" / "cache" / "namespaces" / "invalidate").POST <@ (user1)
val response = makePostRequest(request, write(InvalidateCacheNamespaceJsonV600("rd_localised")))
Then("We should get a 403")
response.code should equal(403)
And("error should be " + UserHasMissingRoles + CanInvalidateCacheNamespace)
response.body.extract[ErrorMessage].message should equal(UserHasMissingRoles + CanInvalidateCacheNamespace)
}
}
feature(s"test $ApiEndpoint3 version $VersionOfApi - Invalid JSON format") {
scenario("We call invalidateCacheNamespace with invalid JSON", ApiEndpoint3, VersionOfApi) {
Given("We have a user with CanInvalidateCacheNamespace entitlement")
Entitlement.entitlement.vend.addEntitlement("", resourceUser1.userId, CanInvalidateCacheNamespace.toString)
When("We make a request with invalid JSON")
val request = (v6_0_0_Request / "management" / "cache" / "namespaces" / "invalidate").POST <@ (user1)
val response = makePostRequest(request, """{"invalid": "json"}""")
Then("We should get a 400")
response.code should equal(400)
And("error should be InvalidJsonFormat")
response.body.extract[ErrorMessage].message should startWith(InvalidJsonFormat)
}
}
feature(s"test $ApiEndpoint3 version $VersionOfApi - Invalid namespace_id") {
scenario("We call invalidateCacheNamespace with non-existent namespace_id", ApiEndpoint3, VersionOfApi) {
Given("We have a user with CanInvalidateCacheNamespace entitlement")
Entitlement.entitlement.vend.addEntitlement("", resourceUser1.userId, CanInvalidateCacheNamespace.toString)
When("We make a request with invalid namespace_id")
val request = (v6_0_0_Request / "management" / "cache" / "namespaces" / "invalidate").POST <@ (user1)
val response = makePostRequest(request, write(InvalidateCacheNamespaceJsonV600("invalid_namespace")))
Then("We should get a 400")
response.code should equal(400)
And("error should mention invalid namespace_id")
val errorMessage = response.body.extract[ErrorMessage].message
errorMessage should include("Invalid namespace_id")
errorMessage should include("invalid_namespace")
}
}
feature(s"test $ApiEndpoint3 version $VersionOfApi - Authorized access with valid namespace") {
scenario("We call invalidateCacheNamespace with valid rd_localised namespace", ApiEndpoint3, VersionOfApi) {
Given("We have a user with CanInvalidateCacheNamespace entitlement")
Entitlement.entitlement.vend.addEntitlement("", resourceUser1.userId, CanInvalidateCacheNamespace.toString)
When("We make a request with valid namespace_id")
val request = (v6_0_0_Request / "management" / "cache" / "namespaces" / "invalidate").POST <@ (user1)
val response = makePostRequest(request, write(InvalidateCacheNamespaceJsonV600("rd_localised")))
Then("We should get a 200")
response.code should equal(200)
And("The response should have the correct structure")
val result = response.body.extract[InvalidatedCacheNamespaceJsonV600]
result.namespace_id should equal("rd_localised")
result.old_version should be > 0L
result.new_version should be > result.old_version
result.new_version should equal(result.old_version + 1)
result.status should equal("invalidated")
}
scenario("We call invalidateCacheNamespace with valid connector namespace", ApiEndpoint3, VersionOfApi) {
Given("We have a user with CanInvalidateCacheNamespace entitlement")
Entitlement.entitlement.vend.addEntitlement("", resourceUser1.userId, CanInvalidateCacheNamespace.toString)
When("We make a request with connector namespace_id")
val request = (v6_0_0_Request / "management" / "cache" / "namespaces" / "invalidate").POST <@ (user1)
val response = makePostRequest(request, write(InvalidateCacheNamespaceJsonV600("connector")))
Then("We should get a 200")
response.code should equal(200)
And("The response should have the correct structure")
val result = response.body.extract[InvalidatedCacheNamespaceJsonV600]
result.namespace_id should equal("connector")
result.old_version should be > 0L
result.new_version should be > result.old_version
result.status should equal("invalidated")
}
scenario("We call invalidateCacheNamespace with valid abac_rule namespace", ApiEndpoint3, VersionOfApi) {
Given("We have a user with CanInvalidateCacheNamespace entitlement")
Entitlement.entitlement.vend.addEntitlement("", resourceUser1.userId, CanInvalidateCacheNamespace.toString)
When("We make a request with abac_rule namespace_id")
val request = (v6_0_0_Request / "management" / "cache" / "namespaces" / "invalidate").POST <@ (user1)
val response = makePostRequest(request, write(InvalidateCacheNamespaceJsonV600("abac_rule")))
Then("We should get a 200")
response.code should equal(200)
And("The response should have the correct structure")
val result = response.body.extract[InvalidatedCacheNamespaceJsonV600]
result.namespace_id should equal("abac_rule")
result.status should equal("invalidated")
}
}
feature(s"test $ApiEndpoint3 version $VersionOfApi - Version increment validation") {
scenario("We verify that cache version increments correctly on multiple invalidations", ApiEndpoint3, VersionOfApi) {
Given("We have a user with CanInvalidateCacheNamespace entitlement")
Entitlement.entitlement.vend.addEntitlement("", resourceUser1.userId, CanInvalidateCacheNamespace.toString)
When("We invalidate the same namespace twice")
val request1 = (v6_0_0_Request / "management" / "cache" / "namespaces" / "invalidate").POST <@ (user1)
val response1 = makePostRequest(request1, write(InvalidateCacheNamespaceJsonV600("rd_dynamic")))
Then("First invalidation should succeed")
response1.code should equal(200)
val result1 = response1.body.extract[InvalidatedCacheNamespaceJsonV600]
val firstNewVersion = result1.new_version
When("We invalidate again")
val request2 = (v6_0_0_Request / "management" / "cache" / "namespaces" / "invalidate").POST <@ (user1)
val response2 = makePostRequest(request2, write(InvalidateCacheNamespaceJsonV600("rd_dynamic")))
Then("Second invalidation should succeed")
response2.code should equal(200)
val result2 = response2.body.extract[InvalidatedCacheNamespaceJsonV600]
And("Version should have incremented again")
result2.old_version should equal(firstNewVersion)
result2.new_version should equal(firstNewVersion + 1)
result2.status should equal("invalidated")
}
}
// ============================================================================================================
// Cross-endpoint test - Verify cache info updates after invalidation
// ============================================================================================================
feature(s"Integration test - Cache endpoints interaction") {
scenario("We verify cache info shows updated version after invalidation", ApiEndpoint2, ApiEndpoint3, VersionOfApi) {
Given("We have a user with both CanGetCacheInfo and CanInvalidateCacheNamespace entitlements")
Entitlement.entitlement.vend.addEntitlement("", resourceUser1.userId, CanGetCacheInfo.toString)
Entitlement.entitlement.vend.addEntitlement("", resourceUser1.userId, CanInvalidateCacheNamespace.toString)
When("We get the initial cache info")
val getRequest1 = (v6_0_0_Request / "system" / "cache" / "info").GET <@ (user1)
val getResponse1 = makeGetRequest(getRequest1)
getResponse1.code should equal(200)
val cacheInfo1 = getResponse1.body.extract[CacheInfoJsonV600]
// Find the rd_static namespace (or any other valid namespace)
val targetNamespace = "rd_static"
val initialVersion = cacheInfo1.namespaces.find(_.namespace_id == targetNamespace).map(_.current_version)
When("We invalidate the namespace")
val invalidateRequest = (v6_0_0_Request / "management" / "cache" / "namespaces" / "invalidate").POST <@ (user1)
val invalidateResponse = makePostRequest(invalidateRequest, write(InvalidateCacheNamespaceJsonV600(targetNamespace)))
invalidateResponse.code should equal(200)
val invalidateResult = invalidateResponse.body.extract[InvalidatedCacheNamespaceJsonV600]
When("We get the cache info again")
val getRequest2 = (v6_0_0_Request / "system" / "cache" / "info").GET <@ (user1)
val getResponse2 = makeGetRequest(getRequest2)
getResponse2.code should equal(200)
val cacheInfo2 = getResponse2.body.extract[CacheInfoJsonV600]
Then("The namespace version should have been incremented")
val updatedNamespace = cacheInfo2.namespaces.find(_.namespace_id == targetNamespace)
updatedNamespace should not be None
if (initialVersion.isDefined) {
updatedNamespace.get.current_version should be > initialVersion.get
}
updatedNamespace.get.current_version should equal(invalidateResult.new_version)
}
}
}