diff --git a/obp-api/src/main/scala/code/api/cache/RedisLogger.scala b/obp-api/src/main/scala/code/api/cache/RedisLogger.scala index 2e778a49c..ee9b58c3a 100644 --- a/obp-api/src/main/scala/code/api/cache/RedisLogger.scala +++ b/obp-api/src/main/scala/code/api/cache/RedisLogger.scala @@ -300,6 +300,24 @@ object RedisLogger { }.getOrElse(LogTail(Nil)) } + /** + * Read latest messages from Redis FIFO queue with pagination support. + */ + def getLogTail(level: LogLevel.LogLevel, limit: Option[Int], offset: Option[Int]): LogTail = { + val fullLogTail = getLogTail(level) + val entries = fullLogTail.entries + + // Apply pagination + val paginatedEntries = (offset, limit) match { + case (Some(off), Some(lim)) => entries.drop(off).take(lim) + case (Some(off), None) => entries.drop(off) + case (None, Some(lim)) => entries.take(lim) + case (None, None) => entries + } + + LogTail(paginatedEntries) + } + /** * Get Redis logging statistics */ diff --git a/obp-api/src/main/scala/code/api/v5_1_0/APIMethods510.scala b/obp-api/src/main/scala/code/api/v5_1_0/APIMethods510.scala index 3ca162f27..22fab2d20 100644 --- a/obp-api/src/main/scala/code/api/v5_1_0/APIMethods510.scala +++ b/obp-api/src/main/scala/code/api/v5_1_0/APIMethods510.scala @@ -248,6 +248,12 @@ trait APIMethods510 { """Returns information about: | |* Log Cache + | + |This endpoint supports pagination via the following optional query parameters: + |* limit - Maximum number of log entries to return + |* offset - Number of log entries to skip (for pagination) + | + |Example: GET /dev-ops/log-cache/INFO?limit=50&offset=100 """, EmptyBody, EmptyBody, @@ -271,8 +277,13 @@ trait APIMethods510 { roles = RedisLogger.LogLevel.requiredRoles(level), callContext = cc.callContext ) - // Fetch logs - logs <- Future(RedisLogger.getLogTail(level)) + httpParams <- NewStyle.function.extractHttpParamsFromUrl(cc.url) + (obpQueryParams, callContext) <- createQueriesByHttpParamsFuture(httpParams, cc.callContext) + // Extract limit and offset from query parameters + limit = obpQueryParams.collectFirst { case OBPLimit(value) => value } + offset = obpQueryParams.collectFirst { case OBPOffset(value) => value } + // Fetch logs with pagination + logs <- Future(RedisLogger.getLogTail(level, limit, offset)) } yield { (logs, HttpCode.`200`(cc.callContext)) } diff --git a/obp-api/src/test/scala/code/api/v5_1_0/LogCacheEndpointTest.scala b/obp-api/src/test/scala/code/api/v5_1_0/LogCacheEndpointTest.scala new file mode 100644 index 000000000..4fdf725ed --- /dev/null +++ b/obp-api/src/test/scala/code/api/v5_1_0/LogCacheEndpointTest.scala @@ -0,0 +1,245 @@ +package code.api.v5_1_0 + +import code.api.util.APIUtil.OAuth._ +import code.api.util.ApiRole.CanGetAllLevelLogsAtAllBanks +import code.api.util.ErrorMessages.{UserHasMissingRoles, UserNotLoggedIn} +import code.api.v5_1_0.OBPAPI5_1_0.Implementations5_1_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.JsonAST._ +import org.scalatest.Tag + +class LogCacheEndpointTest extends V510ServerSetup { + + /** + * Test tags + * Example: To run tests with tag "logCacheEndpoint": + * mvn test -D tagsToInclude + * + * This is made possible by the scalatest maven plugin + */ + object VersionOfApi extends Tag(ApiVersion.v5_1_0.toString) + object ApiEndpoint1 extends Tag(nameOf(Implementations5_1_0.logCacheEndpoint)) + + feature(s"test $ApiEndpoint1 version $VersionOfApi - Unauthorized access") { + scenario("We will call the endpoint without user credentials", ApiEndpoint1, VersionOfApi) { + When("We make a request v5.1.0") + val request = (v5_1_0_Request / "dev-ops" / "log-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 $ApiEndpoint1 version $VersionOfApi - Missing entitlement") { + scenario("We will call the endpoint with user credentials but without proper entitlement", ApiEndpoint1, VersionOfApi) { + When("We make a request v5.1.0") + val request = (v5_1_0_Request / "dev-ops" / "log-cache" / "INFO").GET <@(user1) + val response = makeGetRequest(request) + Then("error should be " + UserHasMissingRoles + CanGetAllLevelLogsAtAllBanks) + response.code should equal(403) + response.body.extract[ErrorMessage].message should be(UserHasMissingRoles + CanGetAllLevelLogsAtAllBanks) + } + } + + feature(s"test $ApiEndpoint1 version $VersionOfApi - Authorized access without pagination") { + scenario("We get log cache without pagination parameters", ApiEndpoint1, VersionOfApi) { + Given("We have a user with proper entitlement") + Entitlement.entitlement.vend.addEntitlement("", resourceUser1.userId, CanGetAllLevelLogsAtAllBanks.toString) + + When("We make a request to get log cache") + val request = (v5_1_0_Request / "dev-ops" / "log-cache" / "INFO").GET <@(user1) + val response = makeGetRequest(request) + + Then("We should get a successful response") + response.code should equal(200) + val json = response.body.extract[JObject] + + And("The response should contain log entries") + (json \ "entries") should not be JNothing + } + } + + feature(s"test $ApiEndpoint1 version $VersionOfApi - Authorized access with limit parameter") { + scenario("We get log cache with limit parameter only", ApiEndpoint1, VersionOfApi) { + Given("We have a user with proper entitlement") + Entitlement.entitlement.vend.addEntitlement("", resourceUser1.userId, CanGetAllLevelLogsAtAllBanks.toString) + + When("We make a request with limit parameter") + val request = (v5_1_0_Request / "dev-ops" / "log-cache" / "INFO").GET <@(user1) <= 0 + } + + scenario("We get log cache with minimum valid limit", ApiEndpoint1, VersionOfApi) { + Given("We have a user with proper entitlement") + Entitlement.entitlement.vend.addEntitlement("", resourceUser1.userId, CanGetAllLevelLogsAtAllBanks.toString) + + When("We make a request with minimum valid limit (1)") + val request = (v5_1_0_Request / "dev-ops" / "log-cache" / "INFO").GET <@(user1) < + val request = (v5_1_0_Request / "dev-ops" / "log-cache" / logLevel).GET <@(user1) <