mirror of
https://github.com/OpenBankProject/OBP-API.git
synced 2026-02-06 11:06:49 +00:00
v6.0.0 of dynmaic endpoints with improved json
This commit is contained in:
parent
ceba49c0ea
commit
bfa3917ce1
@ -30,7 +30,7 @@ import code.api.v5_0_0.{ViewJsonV500, ViewsJsonV500}
|
||||
import code.api.v5_1_0.{JSONFactory510, PostCustomerLegalNameJsonV510}
|
||||
import code.api.dynamic.entity.helper.{DynamicEntityHelper, DynamicEntityInfo}
|
||||
import code.api.v6_0_0.JSONFactory600.{AddUserToGroupResponseJsonV600, DynamicEntityDiagnosticsJsonV600, DynamicEntityIssueJsonV600, GroupEntitlementJsonV600, GroupEntitlementsJsonV600, GroupJsonV600, GroupsJsonV600, PostGroupJsonV600, PostGroupMembershipJsonV600, PostResetPasswordUrlJsonV600, PutGroupJsonV600, ReferenceTypeJsonV600, ReferenceTypesJsonV600, ResetPasswordUrlJsonV600, RoleWithEntitlementCountJsonV600, RolesWithEntitlementCountsJsonV600, ScannedApiVersionJsonV600, UpdateViewJsonV600, UserGroupMembershipJsonV600, UserGroupMembershipsJsonV600, ValidateUserEmailJsonV600, ValidateUserEmailResponseJsonV600, ViewJsonV600, ViewPermissionJsonV600, ViewPermissionsJsonV600, ViewsJsonV600, createAbacRuleJsonV600, createAbacRulesJsonV600, createActiveRateLimitsJsonV600, createCallLimitJsonV600, createRedisCallCountersJson}
|
||||
import code.api.v6_0_0.{AbacRuleJsonV600, AbacRuleResultJsonV600, AbacRulesJsonV600, CacheConfigJsonV600, CacheInfoJsonV600, CacheNamespaceInfoJsonV600, CreateAbacRuleJsonV600, CurrentConsumerJsonV600, DynamicEntityDefinitionJsonV600, ExecuteAbacRuleJsonV600, InMemoryCacheStatusJsonV600, MyDynamicEntitiesJsonV600, RedisCacheStatusJsonV600, UpdateAbacRuleJsonV600}
|
||||
import code.api.v6_0_0.{AbacRuleJsonV600, AbacRuleResultJsonV600, AbacRulesJsonV600, CacheConfigJsonV600, CacheInfoJsonV600, CacheNamespaceInfoJsonV600, CreateAbacRuleJsonV600, CreateDynamicEntityRequestJsonV600, CurrentConsumerJsonV600, DynamicEntityDefinitionJsonV600, DynamicEntityDefinitionWithCountJsonV600, DynamicEntitiesWithCountJsonV600, ExecuteAbacRuleJsonV600, InMemoryCacheStatusJsonV600, MyDynamicEntitiesJsonV600, RedisCacheStatusJsonV600, UpdateAbacRuleJsonV600, UpdateDynamicEntityRequestJsonV600}
|
||||
import code.api.v6_0_0.OBPAPI6_0_0
|
||||
import code.abacrule.{AbacRuleEngine, MappedAbacRuleProvider}
|
||||
import code.metrics.APIMetrics
|
||||
@ -4557,13 +4557,24 @@ trait APIMethods600 {
|
||||
|
|
||||
|Each dynamic entity in the response includes a `record_count` field showing how many data records exist for that entity.
|
||||
|
|
||||
|This v6.0.0 endpoint returns snake_case field names and an explicit `entity_name` field.
|
||||
|
|
||||
|For more information see ${Glossary.getGlossaryItemLink(
|
||||
"Dynamic-Entities"
|
||||
)} """,
|
||||
EmptyBody,
|
||||
ListResult(
|
||||
"dynamic_entities",
|
||||
List(dynamicEntityResponseBodyExample)
|
||||
DynamicEntitiesWithCountJsonV600(
|
||||
dynamic_entities = List(
|
||||
DynamicEntityDefinitionWithCountJsonV600(
|
||||
dynamic_entity_id = "abc-123-def",
|
||||
entity_name = "CustomerPreferences",
|
||||
user_id = "user-456",
|
||||
bank_id = None,
|
||||
has_personal_entity = true,
|
||||
definition = net.liftweb.json.parse("""{"description": "User preferences", "required": ["theme"], "properties": {"theme": {"type": "string"}, "language": {"type": "string"}}}""").asInstanceOf[net.liftweb.json.JsonAST.JObject],
|
||||
record_count = 42
|
||||
)
|
||||
)
|
||||
),
|
||||
List(
|
||||
$AuthenticatedUserIsRequired,
|
||||
@ -4584,16 +4595,82 @@ trait APIMethods600 {
|
||||
)
|
||||
} yield {
|
||||
val listCommons: List[DynamicEntityCommons] = dynamicEntities.sortBy(_.entityName)
|
||||
val jObjectsWithCounts = listCommons.map { entity =>
|
||||
val entitiesWithCounts = listCommons.map { entity =>
|
||||
val recordCount = DynamicData.count(
|
||||
By(DynamicData.DynamicEntityName, entity.entityName),
|
||||
By(DynamicData.IsPersonalEntity, false),
|
||||
if (entity.bankId.isEmpty) NullRef(DynamicData.BankId) else By(DynamicData.BankId, entity.bankId.get)
|
||||
)
|
||||
entity.jValue.asInstanceOf[JObject] ~ ("record_count" -> recordCount)
|
||||
(entity, recordCount)
|
||||
}
|
||||
(
|
||||
ListResult("dynamic_entities", jObjectsWithCounts),
|
||||
JSONFactory600.createDynamicEntitiesWithCountJson(entitiesWithCounts),
|
||||
HttpCode.`200`(cc.callContext)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
staticResourceDocs += ResourceDoc(
|
||||
getBankLevelDynamicEntities,
|
||||
implementedInApiVersion,
|
||||
nameOf(getBankLevelDynamicEntities),
|
||||
"GET",
|
||||
"/management/banks/BANK_ID/dynamic-entities",
|
||||
"Get Bank Level Dynamic Entities",
|
||||
s"""Get all Bank Level Dynamic Entities for one bank with record counts.
|
||||
|
|
||||
|Each dynamic entity in the response includes a `record_count` field showing how many data records exist for that entity.
|
||||
|
|
||||
|This v6.0.0 endpoint returns snake_case field names and an explicit `entity_name` field.
|
||||
|
|
||||
|For more information see ${Glossary.getGlossaryItemLink(
|
||||
"Dynamic-Entities"
|
||||
)} """,
|
||||
EmptyBody,
|
||||
DynamicEntitiesWithCountJsonV600(
|
||||
dynamic_entities = List(
|
||||
DynamicEntityDefinitionWithCountJsonV600(
|
||||
dynamic_entity_id = "abc-123-def",
|
||||
entity_name = "CustomerPreferences",
|
||||
user_id = "user-456",
|
||||
bank_id = Some("gh.29.uk"),
|
||||
has_personal_entity = true,
|
||||
definition = net.liftweb.json.parse("""{"description": "User preferences", "required": ["theme"], "properties": {"theme": {"type": "string"}, "language": {"type": "string"}}}""").asInstanceOf[net.liftweb.json.JsonAST.JObject],
|
||||
record_count = 42
|
||||
)
|
||||
)
|
||||
),
|
||||
List(
|
||||
$BankNotFound,
|
||||
$UserNotLoggedIn,
|
||||
UserHasMissingRoles,
|
||||
UnknownError
|
||||
),
|
||||
List(apiTagManageDynamicEntity, apiTagApi),
|
||||
Some(List(canGetBankLevelDynamicEntities))
|
||||
)
|
||||
|
||||
lazy val getBankLevelDynamicEntities: OBPEndpoint = {
|
||||
case "management" :: "banks" :: bankId :: "dynamic-entities" :: Nil JsonGet req => {
|
||||
cc =>
|
||||
implicit val ec = EndpointContext(Some(cc))
|
||||
for {
|
||||
dynamicEntities <- Future(
|
||||
NewStyle.function.getDynamicEntities(Some(bankId), false)
|
||||
)
|
||||
} yield {
|
||||
val listCommons: List[DynamicEntityCommons] = dynamicEntities.sortBy(_.entityName)
|
||||
val entitiesWithCounts = listCommons.map { entity =>
|
||||
val recordCount = DynamicData.count(
|
||||
By(DynamicData.DynamicEntityName, entity.entityName),
|
||||
By(DynamicData.IsPersonalEntity, false),
|
||||
By(DynamicData.BankId, bankId)
|
||||
)
|
||||
(entity, recordCount)
|
||||
}
|
||||
(
|
||||
JSONFactory600.createDynamicEntitiesWithCountJson(entitiesWithCounts),
|
||||
HttpCode.`200`(cc.callContext)
|
||||
)
|
||||
}
|
||||
@ -4614,6 +4691,397 @@ trait APIMethods600 {
|
||||
box.openOrThrowException(s"$UnknownError ")
|
||||
}
|
||||
|
||||
// Helper method for creating dynamic entities with v6.0.0 response format
|
||||
private def createDynamicEntityV600(
|
||||
cc: CallContext,
|
||||
dynamicEntity: DynamicEntityCommons
|
||||
) = {
|
||||
for {
|
||||
Full(result) <- NewStyle.function.createOrUpdateDynamicEntity(
|
||||
dynamicEntity,
|
||||
cc.callContext
|
||||
)
|
||||
// Grant the CRUD roles to the logged-in user
|
||||
crudRoles = List(
|
||||
DynamicEntityInfo.canCreateRole(result.entityName, dynamicEntity.bankId),
|
||||
DynamicEntityInfo.canUpdateRole(result.entityName, dynamicEntity.bankId),
|
||||
DynamicEntityInfo.canGetRole(result.entityName, dynamicEntity.bankId),
|
||||
DynamicEntityInfo.canDeleteRole(result.entityName, dynamicEntity.bankId)
|
||||
)
|
||||
} yield {
|
||||
crudRoles.map(role =>
|
||||
Entitlement.entitlement.vend.addEntitlement(
|
||||
dynamicEntity.bankId.getOrElse(""),
|
||||
cc.userId,
|
||||
role.toString()
|
||||
)
|
||||
)
|
||||
val commonsData: DynamicEntityCommons = result
|
||||
(
|
||||
JSONFactory600.createMyDynamicEntitiesJson(List(commonsData)).dynamic_entities.head,
|
||||
HttpCode.`201`(cc.callContext)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// Helper method for updating dynamic entities with v6.0.0 response format
|
||||
private def updateDynamicEntityV600(
|
||||
cc: CallContext,
|
||||
dynamicEntity: DynamicEntityCommons
|
||||
) = {
|
||||
for {
|
||||
Full(result) <- NewStyle.function.createOrUpdateDynamicEntity(
|
||||
dynamicEntity,
|
||||
cc.callContext
|
||||
)
|
||||
} yield {
|
||||
val commonsData: DynamicEntityCommons = result
|
||||
(
|
||||
JSONFactory600.createMyDynamicEntitiesJson(List(commonsData)).dynamic_entities.head,
|
||||
HttpCode.`200`(cc.callContext)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
staticResourceDocs += ResourceDoc(
|
||||
createSystemDynamicEntity,
|
||||
implementedInApiVersion,
|
||||
nameOf(createSystemDynamicEntity),
|
||||
"POST",
|
||||
"/management/system-dynamic-entities",
|
||||
"Create System Level Dynamic Entity",
|
||||
s"""Create a system level Dynamic Entity.
|
||||
|
|
||||
|This v6.0.0 endpoint accepts and returns snake_case field names with an explicit `entity_name` field.
|
||||
|
|
||||
|**Request format:**
|
||||
|```json
|
||||
|{
|
||||
| "entity_name": "CustomerPreferences",
|
||||
| "has_personal_entity": true,
|
||||
| "definition": {
|
||||
| "description": "User preferences",
|
||||
| "required": ["theme"],
|
||||
| "properties": {
|
||||
| "theme": {"type": "string", "example": "dark"},
|
||||
| "language": {"type": "string", "example": "en"}
|
||||
| }
|
||||
| }
|
||||
|}
|
||||
|```
|
||||
|
|
||||
|**Important:** Each property MUST include an `example` field with a valid example value.
|
||||
|
|
||||
|For more information see ${Glossary.getGlossaryItemLink("Dynamic-Entities")}""",
|
||||
CreateDynamicEntityRequestJsonV600(
|
||||
entity_name = "CustomerPreferences",
|
||||
has_personal_entity = Some(true),
|
||||
definition = net.liftweb.json.parse("""{"description": "User preferences", "required": ["theme"], "properties": {"theme": {"type": "string", "example": "dark"}, "language": {"type": "string", "example": "en"}}}""").asInstanceOf[net.liftweb.json.JsonAST.JObject]
|
||||
),
|
||||
DynamicEntityDefinitionJsonV600(
|
||||
dynamic_entity_id = "abc-123-def",
|
||||
entity_name = "CustomerPreferences",
|
||||
user_id = "user-456",
|
||||
bank_id = None,
|
||||
has_personal_entity = true,
|
||||
definition = net.liftweb.json.parse("""{"description": "User preferences", "required": ["theme"], "properties": {"theme": {"type": "string", "example": "dark"}, "language": {"type": "string", "example": "en"}}}""").asInstanceOf[net.liftweb.json.JsonAST.JObject]
|
||||
),
|
||||
List(
|
||||
$UserNotLoggedIn,
|
||||
UserHasMissingRoles,
|
||||
InvalidJsonFormat,
|
||||
UnknownError
|
||||
),
|
||||
List(apiTagManageDynamicEntity, apiTagApi),
|
||||
Some(List(canCreateSystemLevelDynamicEntity))
|
||||
)
|
||||
|
||||
lazy val createSystemDynamicEntity: OBPEndpoint = {
|
||||
case "management" :: "system-dynamic-entities" :: Nil JsonPost json -> _ => { cc =>
|
||||
implicit val ec = EndpointContext(Some(cc))
|
||||
for {
|
||||
request <- NewStyle.function.tryons(s"$InvalidJsonFormat", 400, cc.callContext) {
|
||||
json.extract[CreateDynamicEntityRequestJsonV600]
|
||||
}
|
||||
internalJson = JSONFactory600.convertV600RequestToInternal(request)
|
||||
dynamicEntity = DynamicEntityCommons(internalJson, None, cc.userId, None)
|
||||
result <- createDynamicEntityV600(cc, dynamicEntity)
|
||||
} yield result
|
||||
}
|
||||
}
|
||||
|
||||
staticResourceDocs += ResourceDoc(
|
||||
createBankLevelDynamicEntity,
|
||||
implementedInApiVersion,
|
||||
nameOf(createBankLevelDynamicEntity),
|
||||
"POST",
|
||||
"/management/banks/BANK_ID/dynamic-entities",
|
||||
"Create Bank Level Dynamic Entity",
|
||||
s"""Create a bank level Dynamic Entity.
|
||||
|
|
||||
|This v6.0.0 endpoint accepts and returns snake_case field names with an explicit `entity_name` field.
|
||||
|
|
||||
|**Request format:**
|
||||
|```json
|
||||
|{
|
||||
| "entity_name": "CustomerPreferences",
|
||||
| "has_personal_entity": true,
|
||||
| "definition": {
|
||||
| "description": "User preferences",
|
||||
| "required": ["theme"],
|
||||
| "properties": {
|
||||
| "theme": {"type": "string", "example": "dark"},
|
||||
| "language": {"type": "string", "example": "en"}
|
||||
| }
|
||||
| }
|
||||
|}
|
||||
|```
|
||||
|
|
||||
|**Important:** Each property MUST include an `example` field with a valid example value.
|
||||
|
|
||||
|For more information see ${Glossary.getGlossaryItemLink("Dynamic-Entities")}""",
|
||||
CreateDynamicEntityRequestJsonV600(
|
||||
entity_name = "CustomerPreferences",
|
||||
has_personal_entity = Some(true),
|
||||
definition = net.liftweb.json.parse("""{"description": "User preferences", "required": ["theme"], "properties": {"theme": {"type": "string", "example": "dark"}, "language": {"type": "string", "example": "en"}}}""").asInstanceOf[net.liftweb.json.JsonAST.JObject]
|
||||
),
|
||||
DynamicEntityDefinitionJsonV600(
|
||||
dynamic_entity_id = "abc-123-def",
|
||||
entity_name = "CustomerPreferences",
|
||||
user_id = "user-456",
|
||||
bank_id = Some("gh.29.uk"),
|
||||
has_personal_entity = true,
|
||||
definition = net.liftweb.json.parse("""{"description": "User preferences", "required": ["theme"], "properties": {"theme": {"type": "string", "example": "dark"}, "language": {"type": "string", "example": "en"}}}""").asInstanceOf[net.liftweb.json.JsonAST.JObject]
|
||||
),
|
||||
List(
|
||||
$BankNotFound,
|
||||
$UserNotLoggedIn,
|
||||
UserHasMissingRoles,
|
||||
InvalidJsonFormat,
|
||||
UnknownError
|
||||
),
|
||||
List(apiTagManageDynamicEntity, apiTagApi),
|
||||
Some(List(canCreateBankLevelDynamicEntity))
|
||||
)
|
||||
|
||||
lazy val createBankLevelDynamicEntity: OBPEndpoint = {
|
||||
case "management" :: "banks" :: bankId :: "dynamic-entities" :: Nil JsonPost json -> _ => { cc =>
|
||||
implicit val ec = EndpointContext(Some(cc))
|
||||
for {
|
||||
request <- NewStyle.function.tryons(s"$InvalidJsonFormat", 400, cc.callContext) {
|
||||
json.extract[CreateDynamicEntityRequestJsonV600]
|
||||
}
|
||||
internalJson = JSONFactory600.convertV600RequestToInternal(request)
|
||||
dynamicEntity = DynamicEntityCommons(internalJson, None, cc.userId, Some(bankId))
|
||||
result <- createDynamicEntityV600(cc, dynamicEntity)
|
||||
} yield result
|
||||
}
|
||||
}
|
||||
|
||||
staticResourceDocs += ResourceDoc(
|
||||
updateSystemDynamicEntity,
|
||||
implementedInApiVersion,
|
||||
nameOf(updateSystemDynamicEntity),
|
||||
"PUT",
|
||||
"/management/system-dynamic-entities/DYNAMIC_ENTITY_ID",
|
||||
"Update System Level Dynamic Entity",
|
||||
s"""Update a system level Dynamic Entity.
|
||||
|
|
||||
|This v6.0.0 endpoint accepts and returns snake_case field names with an explicit `entity_name` field.
|
||||
|
|
||||
|**Request format:**
|
||||
|```json
|
||||
|{
|
||||
| "entity_name": "CustomerPreferences",
|
||||
| "has_personal_entity": true,
|
||||
| "definition": {
|
||||
| "description": "User preferences updated",
|
||||
| "required": ["theme"],
|
||||
| "properties": {
|
||||
| "theme": {"type": "string", "example": "dark"},
|
||||
| "language": {"type": "string", "example": "en"},
|
||||
| "notifications_enabled": {"type": "boolean", "example": "true"}
|
||||
| }
|
||||
| }
|
||||
|}
|
||||
|```
|
||||
|
|
||||
|For more information see ${Glossary.getGlossaryItemLink("Dynamic-Entities")}""",
|
||||
UpdateDynamicEntityRequestJsonV600(
|
||||
entity_name = "CustomerPreferences",
|
||||
has_personal_entity = Some(true),
|
||||
definition = net.liftweb.json.parse("""{"description": "User preferences updated", "required": ["theme"], "properties": {"theme": {"type": "string", "example": "dark"}, "language": {"type": "string", "example": "en"}, "notifications_enabled": {"type": "boolean", "example": "true"}}}""").asInstanceOf[net.liftweb.json.JsonAST.JObject]
|
||||
),
|
||||
DynamicEntityDefinitionJsonV600(
|
||||
dynamic_entity_id = "abc-123-def",
|
||||
entity_name = "CustomerPreferences",
|
||||
user_id = "user-456",
|
||||
bank_id = None,
|
||||
has_personal_entity = true,
|
||||
definition = net.liftweb.json.parse("""{"description": "User preferences updated", "required": ["theme"], "properties": {"theme": {"type": "string", "example": "dark"}, "language": {"type": "string", "example": "en"}, "notifications_enabled": {"type": "boolean", "example": "true"}}}""").asInstanceOf[net.liftweb.json.JsonAST.JObject]
|
||||
),
|
||||
List(
|
||||
$UserNotLoggedIn,
|
||||
UserHasMissingRoles,
|
||||
InvalidJsonFormat,
|
||||
UnknownError
|
||||
),
|
||||
List(apiTagManageDynamicEntity, apiTagApi),
|
||||
Some(List(canUpdateSystemDynamicEntity))
|
||||
)
|
||||
|
||||
lazy val updateSystemDynamicEntity: OBPEndpoint = {
|
||||
case "management" :: "system-dynamic-entities" :: dynamicEntityId :: Nil JsonPut json -> _ => { cc =>
|
||||
implicit val ec = EndpointContext(Some(cc))
|
||||
for {
|
||||
request <- NewStyle.function.tryons(s"$InvalidJsonFormat", 400, cc.callContext) {
|
||||
json.extract[UpdateDynamicEntityRequestJsonV600]
|
||||
}
|
||||
internalJson = JSONFactory600.convertV600UpdateRequestToInternal(request)
|
||||
dynamicEntity = DynamicEntityCommons(internalJson, Some(dynamicEntityId), cc.userId, None)
|
||||
result <- updateDynamicEntityV600(cc, dynamicEntity)
|
||||
} yield result
|
||||
}
|
||||
}
|
||||
|
||||
staticResourceDocs += ResourceDoc(
|
||||
updateBankLevelDynamicEntity,
|
||||
implementedInApiVersion,
|
||||
nameOf(updateBankLevelDynamicEntity),
|
||||
"PUT",
|
||||
"/management/banks/BANK_ID/dynamic-entities/DYNAMIC_ENTITY_ID",
|
||||
"Update Bank Level Dynamic Entity",
|
||||
s"""Update a bank level Dynamic Entity.
|
||||
|
|
||||
|This v6.0.0 endpoint accepts and returns snake_case field names with an explicit `entity_name` field.
|
||||
|
|
||||
|**Request format:**
|
||||
|```json
|
||||
|{
|
||||
| "entity_name": "CustomerPreferences",
|
||||
| "has_personal_entity": true,
|
||||
| "definition": {
|
||||
| "description": "User preferences updated",
|
||||
| "required": ["theme"],
|
||||
| "properties": {
|
||||
| "theme": {"type": "string", "example": "dark"},
|
||||
| "language": {"type": "string", "example": "en"},
|
||||
| "notifications_enabled": {"type": "boolean", "example": "true"}
|
||||
| }
|
||||
| }
|
||||
|}
|
||||
|```
|
||||
|
|
||||
|For more information see ${Glossary.getGlossaryItemLink("Dynamic-Entities")}""",
|
||||
UpdateDynamicEntityRequestJsonV600(
|
||||
entity_name = "CustomerPreferences",
|
||||
has_personal_entity = Some(true),
|
||||
definition = net.liftweb.json.parse("""{"description": "User preferences updated", "required": ["theme"], "properties": {"theme": {"type": "string", "example": "dark"}, "language": {"type": "string", "example": "en"}, "notifications_enabled": {"type": "boolean", "example": "true"}}}""").asInstanceOf[net.liftweb.json.JsonAST.JObject]
|
||||
),
|
||||
DynamicEntityDefinitionJsonV600(
|
||||
dynamic_entity_id = "abc-123-def",
|
||||
entity_name = "CustomerPreferences",
|
||||
user_id = "user-456",
|
||||
bank_id = Some("gh.29.uk"),
|
||||
has_personal_entity = true,
|
||||
definition = net.liftweb.json.parse("""{"description": "User preferences updated", "required": ["theme"], "properties": {"theme": {"type": "string", "example": "dark"}, "language": {"type": "string", "example": "en"}, "notifications_enabled": {"type": "boolean", "example": "true"}}}""").asInstanceOf[net.liftweb.json.JsonAST.JObject]
|
||||
),
|
||||
List(
|
||||
$BankNotFound,
|
||||
$UserNotLoggedIn,
|
||||
UserHasMissingRoles,
|
||||
InvalidJsonFormat,
|
||||
UnknownError
|
||||
),
|
||||
List(apiTagManageDynamicEntity, apiTagApi),
|
||||
Some(List(canUpdateBankLevelDynamicEntity))
|
||||
)
|
||||
|
||||
lazy val updateBankLevelDynamicEntity: OBPEndpoint = {
|
||||
case "management" :: "banks" :: bankId :: "dynamic-entities" :: dynamicEntityId :: Nil JsonPut json -> _ => { cc =>
|
||||
implicit val ec = EndpointContext(Some(cc))
|
||||
for {
|
||||
request <- NewStyle.function.tryons(s"$InvalidJsonFormat", 400, cc.callContext) {
|
||||
json.extract[UpdateDynamicEntityRequestJsonV600]
|
||||
}
|
||||
internalJson = JSONFactory600.convertV600UpdateRequestToInternal(request)
|
||||
dynamicEntity = DynamicEntityCommons(internalJson, Some(dynamicEntityId), cc.userId, Some(bankId))
|
||||
result <- updateDynamicEntityV600(cc, dynamicEntity)
|
||||
} yield result
|
||||
}
|
||||
}
|
||||
|
||||
staticResourceDocs += ResourceDoc(
|
||||
updateMyDynamicEntity,
|
||||
implementedInApiVersion,
|
||||
nameOf(updateMyDynamicEntity),
|
||||
"PUT",
|
||||
"/my/dynamic-entities/DYNAMIC_ENTITY_ID",
|
||||
"Update My Dynamic Entity",
|
||||
s"""Update a Dynamic Entity that I created.
|
||||
|
|
||||
|This v6.0.0 endpoint accepts and returns snake_case field names with an explicit `entity_name` field.
|
||||
|
|
||||
|**Request format:**
|
||||
|```json
|
||||
|{
|
||||
| "entity_name": "CustomerPreferences",
|
||||
| "has_personal_entity": true,
|
||||
| "definition": {
|
||||
| "description": "User preferences updated",
|
||||
| "required": ["theme"],
|
||||
| "properties": {
|
||||
| "theme": {"type": "string", "example": "dark"},
|
||||
| "language": {"type": "string", "example": "en"},
|
||||
| "notifications_enabled": {"type": "boolean", "example": "true"}
|
||||
| }
|
||||
| }
|
||||
|}
|
||||
|```
|
||||
|
|
||||
|For more information see ${Glossary.getGlossaryItemLink("My-Dynamic-Entities")}""",
|
||||
UpdateDynamicEntityRequestJsonV600(
|
||||
entity_name = "CustomerPreferences",
|
||||
has_personal_entity = Some(true),
|
||||
definition = net.liftweb.json.parse("""{"description": "User preferences updated", "required": ["theme"], "properties": {"theme": {"type": "string", "example": "dark"}, "language": {"type": "string", "example": "en"}, "notifications_enabled": {"type": "boolean", "example": "true"}}}""").asInstanceOf[net.liftweb.json.JsonAST.JObject]
|
||||
),
|
||||
DynamicEntityDefinitionJsonV600(
|
||||
dynamic_entity_id = "abc-123-def",
|
||||
entity_name = "CustomerPreferences",
|
||||
user_id = "user-456",
|
||||
bank_id = None,
|
||||
has_personal_entity = true,
|
||||
definition = net.liftweb.json.parse("""{"description": "User preferences updated", "required": ["theme"], "properties": {"theme": {"type": "string", "example": "dark"}, "language": {"type": "string", "example": "en"}, "notifications_enabled": {"type": "boolean", "example": "true"}}}""").asInstanceOf[net.liftweb.json.JsonAST.JObject]
|
||||
),
|
||||
List(
|
||||
$UserNotLoggedIn,
|
||||
InvalidJsonFormat,
|
||||
UnknownError
|
||||
),
|
||||
List(apiTagManageDynamicEntity, apiTagApi)
|
||||
)
|
||||
|
||||
lazy val updateMyDynamicEntity: OBPEndpoint = {
|
||||
case "my" :: "dynamic-entities" :: dynamicEntityId :: Nil JsonPut json -> _ => { cc =>
|
||||
implicit val ec = EndpointContext(Some(cc))
|
||||
for {
|
||||
// Verify the user owns this dynamic entity
|
||||
existingEntity <- Future(
|
||||
NewStyle.function.getDynamicEntitiesByUserId(cc.userId).find(_.dynamicEntityId.contains(dynamicEntityId))
|
||||
)
|
||||
_ <- Helper.booleanToFuture(s"$DynamicEntityNotFoundByDynamicEntityId dynamicEntityId = $dynamicEntityId", cc = cc.callContext) {
|
||||
existingEntity.isDefined
|
||||
}
|
||||
request <- NewStyle.function.tryons(s"$InvalidJsonFormat", 400, cc.callContext) {
|
||||
json.extract[UpdateDynamicEntityRequestJsonV600]
|
||||
}
|
||||
internalJson = JSONFactory600.convertV600UpdateRequestToInternal(request)
|
||||
dynamicEntity = DynamicEntityCommons(internalJson, Some(dynamicEntityId), cc.userId, existingEntity.get.bankId)
|
||||
result <- updateDynamicEntityV600(cc, dynamicEntity)
|
||||
} yield result
|
||||
}
|
||||
}
|
||||
|
||||
staticResourceDocs += ResourceDoc(
|
||||
deleteSystemDynamicEntityCascade,
|
||||
implementedInApiVersion,
|
||||
|
||||
@ -501,6 +501,35 @@ case class MyDynamicEntitiesJsonV600(
|
||||
dynamic_entities: List[DynamicEntityDefinitionJsonV600]
|
||||
)
|
||||
|
||||
// Management version includes record_count for admin visibility
|
||||
case class DynamicEntityDefinitionWithCountJsonV600(
|
||||
dynamic_entity_id: String,
|
||||
entity_name: String,
|
||||
user_id: String,
|
||||
bank_id: Option[String],
|
||||
has_personal_entity: Boolean,
|
||||
definition: net.liftweb.json.JsonAST.JObject,
|
||||
record_count: Long
|
||||
)
|
||||
|
||||
case class DynamicEntitiesWithCountJsonV600(
|
||||
dynamic_entities: List[DynamicEntityDefinitionWithCountJsonV600]
|
||||
)
|
||||
|
||||
// Request format for creating a dynamic entity (v6.0.0 with snake_case)
|
||||
case class CreateDynamicEntityRequestJsonV600(
|
||||
entity_name: String,
|
||||
has_personal_entity: Option[Boolean], // defaults to true if not provided
|
||||
definition: net.liftweb.json.JsonAST.JObject
|
||||
)
|
||||
|
||||
// Request format for updating a dynamic entity (v6.0.0 with snake_case)
|
||||
case class UpdateDynamicEntityRequestJsonV600(
|
||||
entity_name: String,
|
||||
has_personal_entity: Option[Boolean],
|
||||
definition: net.liftweb.json.JsonAST.JObject
|
||||
)
|
||||
|
||||
object JSONFactory600 extends CustomJsonFormats with MdcLoggable {
|
||||
|
||||
def createRedisCallCountersJson(
|
||||
@ -1336,16 +1365,118 @@ object JSONFactory600 extends CustomJsonFormats with MdcLoggable {
|
||||
|
||||
MyDynamicEntitiesJsonV600(
|
||||
dynamic_entities = dynamicEntities.map { entity =>
|
||||
// metadataJson contains the full internal format: { "EntityName": { schema }, "hasPersonalEntity": true }
|
||||
// We need to extract just the schema part using the entity name as key
|
||||
val fullJson = parse(entity.metadataJson).asInstanceOf[JObject]
|
||||
val schemaOption = fullJson.obj.find(_.name == entity.entityName).map(_.value.asInstanceOf[JObject])
|
||||
|
||||
// Validate that the dynamic key matches entity_name
|
||||
val dynamicKeyName = fullJson.obj.find(_.name != "hasPersonalEntity").map(_.name)
|
||||
if (dynamicKeyName.exists(_ != entity.entityName)) {
|
||||
throw new IllegalStateException(
|
||||
s"Dynamic entity key mismatch: stored entityName='${entity.entityName}' but dynamic key='${dynamicKeyName.getOrElse("none")}'"
|
||||
)
|
||||
}
|
||||
|
||||
val schema = schemaOption.getOrElse(
|
||||
throw new IllegalStateException(s"Could not extract schema for entity '${entity.entityName}' from metadataJson")
|
||||
)
|
||||
|
||||
DynamicEntityDefinitionJsonV600(
|
||||
dynamic_entity_id = entity.dynamicEntityId.getOrElse(""),
|
||||
entity_name = entity.entityName,
|
||||
user_id = entity.userId,
|
||||
bank_id = entity.bankId,
|
||||
has_personal_entity = entity.hasPersonalEntity,
|
||||
definition = parse(entity.metadataJson).asInstanceOf[JObject]
|
||||
definition = schema
|
||||
)
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Create v6.0.0 response for management GET endpoints (includes record_count)
|
||||
*/
|
||||
def createDynamicEntitiesWithCountJson(
|
||||
entitiesWithCounts: List[(code.dynamicEntity.DynamicEntityCommons, Long)]
|
||||
): DynamicEntitiesWithCountJsonV600 = {
|
||||
import net.liftweb.json.JsonAST._
|
||||
import net.liftweb.json.parse
|
||||
|
||||
DynamicEntitiesWithCountJsonV600(
|
||||
dynamic_entities = entitiesWithCounts.map { case (entity, recordCount) =>
|
||||
// metadataJson contains the full internal format: { "EntityName": { schema }, "hasPersonalEntity": true }
|
||||
// We need to extract just the schema part using the entity name as key
|
||||
val fullJson = parse(entity.metadataJson).asInstanceOf[JObject]
|
||||
val schemaOption = fullJson.obj.find(_.name == entity.entityName).map(_.value.asInstanceOf[JObject])
|
||||
|
||||
// Validate that the dynamic key matches entity_name
|
||||
val dynamicKeyName = fullJson.obj.find(_.name != "hasPersonalEntity").map(_.name)
|
||||
if (dynamicKeyName.exists(_ != entity.entityName)) {
|
||||
throw new IllegalStateException(
|
||||
s"Dynamic entity key mismatch: stored entityName='${entity.entityName}' but dynamic key='${dynamicKeyName.getOrElse("none")}'"
|
||||
)
|
||||
}
|
||||
|
||||
val schema = schemaOption.getOrElse(
|
||||
throw new IllegalStateException(s"Could not extract schema for entity '${entity.entityName}' from metadataJson")
|
||||
)
|
||||
|
||||
DynamicEntityDefinitionWithCountJsonV600(
|
||||
dynamic_entity_id = entity.dynamicEntityId.getOrElse(""),
|
||||
entity_name = entity.entityName,
|
||||
user_id = entity.userId,
|
||||
bank_id = entity.bankId,
|
||||
has_personal_entity = entity.hasPersonalEntity,
|
||||
definition = schema,
|
||||
record_count = recordCount
|
||||
)
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert v6.0.0 request format to the internal JObject format expected by DynamicEntityCommons.apply
|
||||
*
|
||||
* Input (v6.0.0):
|
||||
* {
|
||||
* "entity_name": "CustomerPreferences",
|
||||
* "has_personal_entity": true,
|
||||
* "definition": { ... schema ... }
|
||||
* }
|
||||
*
|
||||
* Output (internal):
|
||||
* {
|
||||
* "CustomerPreferences": { ... schema ... },
|
||||
* "hasPersonalEntity": true
|
||||
* }
|
||||
*/
|
||||
def convertV600RequestToInternal(request: CreateDynamicEntityRequestJsonV600): net.liftweb.json.JsonAST.JObject = {
|
||||
import net.liftweb.json.JsonAST._
|
||||
import net.liftweb.json.JsonDSL._
|
||||
|
||||
val hasPersonalEntity = request.has_personal_entity.getOrElse(true)
|
||||
|
||||
// Build the internal format: entity name as dynamic key + hasPersonalEntity
|
||||
JObject(
|
||||
JField(request.entity_name, request.definition) ::
|
||||
JField("hasPersonalEntity", JBool(hasPersonalEntity)) ::
|
||||
Nil
|
||||
)
|
||||
}
|
||||
|
||||
def convertV600UpdateRequestToInternal(request: UpdateDynamicEntityRequestJsonV600): net.liftweb.json.JsonAST.JObject = {
|
||||
import net.liftweb.json.JsonAST._
|
||||
import net.liftweb.json.JsonDSL._
|
||||
|
||||
val hasPersonalEntity = request.has_personal_entity.getOrElse(true)
|
||||
|
||||
// Build the internal format: entity name as dynamic key + hasPersonalEntity
|
||||
JObject(
|
||||
JField(request.entity_name, request.definition) ::
|
||||
JField("hasPersonalEntity", JBool(hasPersonalEntity)) ::
|
||||
Nil
|
||||
)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
551
obp-api/src/test/scala/code/api/v6_0_0/DynamicEntityTest.scala
Normal file
551
obp-api/src/test/scala/code/api/v6_0_0/DynamicEntityTest.scala
Normal file
@ -0,0 +1,551 @@
|
||||
/**
|
||||
Open Bank Project - API
|
||||
Copyright (C) 2011-2025, 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._
|
||||
import code.api.util.ErrorMessages._
|
||||
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.JsonDSL._
|
||||
import net.liftweb.json.Serialization.write
|
||||
import net.liftweb.json._
|
||||
import org.scalatest.Tag
|
||||
|
||||
class DynamicEntityTest extends V600ServerSetup {
|
||||
|
||||
override def beforeAll(): Unit = {
|
||||
super.beforeAll()
|
||||
}
|
||||
|
||||
override def afterAll(): Unit = {
|
||||
super.afterAll()
|
||||
}
|
||||
|
||||
/**
|
||||
* Test tags
|
||||
* Example: To run tests with tag "getPermissions":
|
||||
* 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.createSystemDynamicEntity))
|
||||
object ApiEndpoint2 extends Tag(nameOf(Implementations6_0_0.updateSystemDynamicEntity))
|
||||
object ApiEndpoint3 extends Tag(nameOf(Implementations6_0_0.getSystemDynamicEntities))
|
||||
object ApiEndpoint4 extends Tag(nameOf(Implementations6_0_0.createBankLevelDynamicEntity))
|
||||
object ApiEndpoint5 extends Tag(nameOf(Implementations6_0_0.updateBankLevelDynamicEntity))
|
||||
object ApiEndpoint6 extends Tag(nameOf(Implementations6_0_0.getBankLevelDynamicEntities))
|
||||
object ApiEndpoint7 extends Tag(nameOf(Implementations6_0_0.getMyDynamicEntities))
|
||||
object ApiEndpoint8 extends Tag(nameOf(Implementations6_0_0.updateMyDynamicEntity))
|
||||
object ApiEndpoint9 extends Tag(nameOf(Implementations6_0_0.getAvailablePersonalDynamicEntities))
|
||||
|
||||
lazy val bankId = testBankId1.value
|
||||
|
||||
// v6.0.0 request format with snake_case and explicit entity_name
|
||||
val rightEntityV600 = parse(
|
||||
"""
|
||||
|{
|
||||
| "entity_name": "FooBar",
|
||||
| "has_personal_entity": true,
|
||||
| "definition": {
|
||||
| "description": "description of this entity, can be markdown text.",
|
||||
| "required": [
|
||||
| "name"
|
||||
| ],
|
||||
| "properties": {
|
||||
| "name": {
|
||||
| "type": "string",
|
||||
| "maxLength": 20,
|
||||
| "minLength": 3,
|
||||
| "example": "James Brown",
|
||||
| "description":"description of **name** field, can be markdown text."
|
||||
| },
|
||||
| "number": {
|
||||
| "type": "integer",
|
||||
| "example": 69876172
|
||||
| }
|
||||
| }
|
||||
| }
|
||||
|}
|
||||
|""".stripMargin)
|
||||
|
||||
// Entity with hasPersonalEntity = false
|
||||
val entityWithoutPersonalV600 = parse(
|
||||
"""
|
||||
|{
|
||||
| "entity_name": "SharedEntity",
|
||||
| "has_personal_entity": false,
|
||||
| "definition": {
|
||||
| "description": "A shared entity without personal endpoints.",
|
||||
| "required": [
|
||||
| "title"
|
||||
| ],
|
||||
| "properties": {
|
||||
| "title": {
|
||||
| "type": "string",
|
||||
| "example": "Some Title"
|
||||
| }
|
||||
| }
|
||||
| }
|
||||
|}
|
||||
|""".stripMargin)
|
||||
|
||||
// Wrong format - missing required field
|
||||
val wrongRequiredEntityV600 = parse(
|
||||
"""
|
||||
|{
|
||||
| "entity_name": "FooBar",
|
||||
| "has_personal_entity": true,
|
||||
| "definition": {
|
||||
| "description": "description of this entity.",
|
||||
| "required": [
|
||||
| "name_wrong"
|
||||
| ],
|
||||
| "properties": {
|
||||
| "name": {
|
||||
| "type": "string",
|
||||
| "example": "James Brown"
|
||||
| }
|
||||
| }
|
||||
| }
|
||||
|}
|
||||
|""".stripMargin)
|
||||
|
||||
// Updated entity for PUT tests
|
||||
val updatedEntityV600 = parse(
|
||||
"""
|
||||
|{
|
||||
| "entity_name": "FooBar",
|
||||
| "has_personal_entity": true,
|
||||
| "definition": {
|
||||
| "description": "Updated description of this entity.",
|
||||
| "required": [
|
||||
| "name"
|
||||
| ],
|
||||
| "properties": {
|
||||
| "name": {
|
||||
| "type": "string",
|
||||
| "maxLength": 30,
|
||||
| "minLength": 2,
|
||||
| "example": "Updated Name",
|
||||
| "description":"Updated description of **name** field."
|
||||
| },
|
||||
| "number": {
|
||||
| "type": "integer",
|
||||
| "example": 12345678
|
||||
| }
|
||||
| }
|
||||
| }
|
||||
|}
|
||||
|""".stripMargin)
|
||||
|
||||
|
||||
feature("v6.0.0 System Level Dynamic Entity endpoints with snake_case JSON") {
|
||||
|
||||
scenario("Create System Dynamic Entity - without user credentials", ApiEndpoint1, VersionOfApi) {
|
||||
When(s"We make a POST request without user credentials")
|
||||
val request = (v6_0_0_Request / "management" / "system-dynamic-entities").POST
|
||||
val response = makePostRequest(request, write(rightEntityV600))
|
||||
Then("We should get a 401")
|
||||
response.code should equal(401)
|
||||
And("error should be " + UserNotLoggedIn)
|
||||
response.body.extract[ErrorMessage].message should equal(UserNotLoggedIn)
|
||||
}
|
||||
|
||||
scenario("Create System Dynamic Entity - without proper role", ApiEndpoint1, VersionOfApi) {
|
||||
When(s"We make a POST request without the role " + CanCreateSystemLevelDynamicEntity)
|
||||
val request = (v6_0_0_Request / "management" / "system-dynamic-entities").POST <@(user1)
|
||||
val response = makePostRequest(request, write(rightEntityV600))
|
||||
Then("We should get a 403")
|
||||
response.code should equal(403)
|
||||
And("error should contain " + UserHasMissingRoles)
|
||||
response.body.extract[ErrorMessage].message should include(UserHasMissingRoles)
|
||||
}
|
||||
|
||||
scenario("Create and verify v6.0.0 snake_case response format", ApiEndpoint1, ApiEndpoint3, VersionOfApi) {
|
||||
Entitlement.entitlement.vend.addEntitlement("", resourceUser1.userId, CanCreateSystemLevelDynamicEntity.toString)
|
||||
|
||||
When("We create a dynamic entity with v6.0.0 format")
|
||||
val request = (v6_0_0_Request / "management" / "system-dynamic-entities").POST <@(user1)
|
||||
val response = makePostRequest(request, write(rightEntityV600))
|
||||
|
||||
Then("We should get a 201")
|
||||
response.code should equal(201)
|
||||
|
||||
val responseJson = response.body
|
||||
|
||||
// Verify snake_case field names exist
|
||||
And("Response should have snake_case field: dynamic_entity_id")
|
||||
(responseJson \ "dynamic_entity_id") shouldBe a[JString]
|
||||
|
||||
And("Response should have snake_case field: entity_name")
|
||||
(responseJson \ "entity_name").extract[String] should equal("FooBar")
|
||||
|
||||
And("Response should have snake_case field: user_id")
|
||||
(responseJson \ "user_id").extract[String] should equal(resourceUser1.userId)
|
||||
|
||||
And("Response should have snake_case field: has_personal_entity")
|
||||
(responseJson \ "has_personal_entity").extract[Boolean] should equal(true)
|
||||
|
||||
And("Response should have definition field with just the schema (no entity name wrapper)")
|
||||
val definition = responseJson \ "definition"
|
||||
(definition \ "description") shouldBe a[JString]
|
||||
(definition \ "required") shouldBe a[JArray]
|
||||
(definition \ "properties") shouldBe a[JObject]
|
||||
|
||||
// Verify definition does NOT contain the entity name as a key (old format would have FooBar as key)
|
||||
And("Definition should NOT contain entity name as a dynamic key")
|
||||
(definition \ "FooBar") should equal(JNothing)
|
||||
|
||||
val dynamicEntityId = (responseJson \ "dynamic_entity_id").extract[String]
|
||||
|
||||
// Now test GET to verify the response format is consistent
|
||||
Entitlement.entitlement.vend.addEntitlement("", resourceUser1.userId, CanGetSystemLevelDynamicEntities.toString)
|
||||
|
||||
When("We GET system dynamic entities")
|
||||
val getRequest = (v6_0_0_Request / "management" / "system-dynamic-entities").GET <@(user1)
|
||||
val getResponse = makeGetRequest(getRequest)
|
||||
|
||||
Then("We should get a 200")
|
||||
getResponse.code should equal(200)
|
||||
|
||||
val entitiesJson = getResponse.body \ "dynamic_entities"
|
||||
entitiesJson shouldBe a[JArray]
|
||||
|
||||
val entities = entitiesJson.asInstanceOf[JArray].arr
|
||||
entities should have size 1
|
||||
|
||||
val entity = entities.head
|
||||
And("GET response should also use snake_case fields")
|
||||
(entity \ "dynamic_entity_id").extract[String] should equal(dynamicEntityId)
|
||||
(entity \ "entity_name").extract[String] should equal("FooBar")
|
||||
(entity \ "has_personal_entity").extract[Boolean] should equal(true)
|
||||
|
||||
And("GET response should include record_count field")
|
||||
(entity \ "record_count") shouldBe a[JInt]
|
||||
|
||||
// Cleanup
|
||||
Entitlement.entitlement.vend.addEntitlement("", resourceUser1.userId, CanDeleteSystemLevelDynamicEntity.toString)
|
||||
val deleteRequest = (v4_0_0_Request / "management" / "system-dynamic-entities" / dynamicEntityId).DELETE <@(user1)
|
||||
makeDeleteRequest(deleteRequest)
|
||||
}
|
||||
|
||||
scenario("Update System Dynamic Entity with v6.0.0 format", ApiEndpoint1, ApiEndpoint2, VersionOfApi) {
|
||||
Entitlement.entitlement.vend.addEntitlement("", resourceUser1.userId, CanCreateSystemLevelDynamicEntity.toString)
|
||||
Entitlement.entitlement.vend.addEntitlement("", resourceUser1.userId, CanUpdateSystemLevelDynamicEntity.toString)
|
||||
|
||||
// Create first
|
||||
val createRequest = (v6_0_0_Request / "management" / "system-dynamic-entities").POST <@(user1)
|
||||
val createResponse = makePostRequest(createRequest, write(rightEntityV600))
|
||||
createResponse.code should equal(201)
|
||||
|
||||
val dynamicEntityId = (createResponse.body \ "dynamic_entity_id").extract[String]
|
||||
|
||||
When("We update the dynamic entity with v6.0.0 format")
|
||||
val updateRequest = (v6_0_0_Request / "management" / "system-dynamic-entities" / dynamicEntityId).PUT <@(user1)
|
||||
val updateResponse = makePutRequest(updateRequest, write(updatedEntityV600))
|
||||
|
||||
Then("We should get a 200")
|
||||
updateResponse.code should equal(200)
|
||||
|
||||
val responseJson = updateResponse.body
|
||||
|
||||
And("Updated response should use snake_case fields")
|
||||
(responseJson \ "dynamic_entity_id").extract[String] should equal(dynamicEntityId)
|
||||
(responseJson \ "entity_name").extract[String] should equal("FooBar")
|
||||
|
||||
And("Definition should be updated")
|
||||
val definition = responseJson \ "definition"
|
||||
(definition \ "description").extract[String] should equal("Updated description of this entity.")
|
||||
|
||||
// Cleanup
|
||||
Entitlement.entitlement.vend.addEntitlement("", resourceUser1.userId, CanDeleteSystemLevelDynamicEntity.toString)
|
||||
val deleteRequest = (v4_0_0_Request / "management" / "system-dynamic-entities" / dynamicEntityId).DELETE <@(user1)
|
||||
makeDeleteRequest(deleteRequest)
|
||||
}
|
||||
|
||||
scenario("Create Dynamic Entity with invalid schema should fail", ApiEndpoint1, VersionOfApi) {
|
||||
Entitlement.entitlement.vend.addEntitlement("", resourceUser1.userId, CanCreateSystemLevelDynamicEntity.toString)
|
||||
|
||||
When("We try to create a dynamic entity with wrong required field")
|
||||
val request = (v6_0_0_Request / "management" / "system-dynamic-entities").POST <@(user1)
|
||||
val response = makePostRequest(request, write(wrongRequiredEntityV600))
|
||||
|
||||
Then("We should get a 400")
|
||||
response.code should equal(400)
|
||||
|
||||
And("Error message should indicate validation failure")
|
||||
response.body.extract[ErrorMessage].message should include(DynamicEntityInstanceValidateFail)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
feature("v6.0.0 Bank Level Dynamic Entity endpoints with snake_case JSON") {
|
||||
|
||||
scenario("Create Bank Level Dynamic Entity - without proper role", ApiEndpoint4, VersionOfApi) {
|
||||
When(s"We make a POST request without the role " + CanCreateBankLevelDynamicEntity)
|
||||
val request = (v6_0_0_Request / "management" / "banks" / bankId / "dynamic-entities").POST <@(user1)
|
||||
val response = makePostRequest(request, write(rightEntityV600))
|
||||
Then("We should get a 403")
|
||||
response.code should equal(403)
|
||||
}
|
||||
|
||||
scenario("Create and GET Bank Level Dynamic Entity with v6.0.0 format", ApiEndpoint4, ApiEndpoint6, VersionOfApi) {
|
||||
Entitlement.entitlement.vend.addEntitlement(bankId, resourceUser1.userId, CanCreateBankLevelDynamicEntity.toString)
|
||||
|
||||
When("We create a bank level dynamic entity with v6.0.0 format")
|
||||
val request = (v6_0_0_Request / "management" / "banks" / bankId / "dynamic-entities").POST <@(user1)
|
||||
val response = makePostRequest(request, write(rightEntityV600))
|
||||
|
||||
Then("We should get a 201")
|
||||
response.code should equal(201)
|
||||
|
||||
val responseJson = response.body
|
||||
|
||||
And("Response should have snake_case field: bank_id")
|
||||
(responseJson \ "bank_id").extract[String] should equal(bankId)
|
||||
|
||||
And("Response should have entity_name")
|
||||
(responseJson \ "entity_name").extract[String] should equal("FooBar")
|
||||
|
||||
val dynamicEntityId = (responseJson \ "dynamic_entity_id").extract[String]
|
||||
|
||||
// Test GET bank level
|
||||
Entitlement.entitlement.vend.addEntitlement(bankId, resourceUser1.userId, CanGetBankLevelDynamicEntities.toString)
|
||||
|
||||
When("We GET bank level dynamic entities")
|
||||
val getRequest = (v6_0_0_Request / "management" / "banks" / bankId / "dynamic-entities").GET <@(user1)
|
||||
val getResponse = makeGetRequest(getRequest)
|
||||
|
||||
Then("We should get a 200")
|
||||
getResponse.code should equal(200)
|
||||
|
||||
val entities = (getResponse.body \ "dynamic_entities").asInstanceOf[JArray].arr
|
||||
entities should have size 1
|
||||
|
||||
val entity = entities.head
|
||||
(entity \ "bank_id").extract[String] should equal(bankId)
|
||||
(entity \ "entity_name").extract[String] should equal("FooBar")
|
||||
(entity \ "record_count") shouldBe a[JInt]
|
||||
|
||||
// Cleanup
|
||||
Entitlement.entitlement.vend.addEntitlement(bankId, resourceUser1.userId, CanDeleteBankLevelDynamicEntity.toString)
|
||||
val deleteRequest = (v4_0_0_Request / "management" / "banks" / bankId / "dynamic-entities" / dynamicEntityId).DELETE <@(user1)
|
||||
makeDeleteRequest(deleteRequest)
|
||||
}
|
||||
|
||||
scenario("Update Bank Level Dynamic Entity with v6.0.0 format", ApiEndpoint4, ApiEndpoint5, VersionOfApi) {
|
||||
Entitlement.entitlement.vend.addEntitlement(bankId, resourceUser1.userId, CanCreateBankLevelDynamicEntity.toString)
|
||||
Entitlement.entitlement.vend.addEntitlement(bankId, resourceUser1.userId, CanUpdateBankLevelDynamicEntity.toString)
|
||||
|
||||
// Create first
|
||||
val createRequest = (v6_0_0_Request / "management" / "banks" / bankId / "dynamic-entities").POST <@(user1)
|
||||
val createResponse = makePostRequest(createRequest, write(rightEntityV600))
|
||||
createResponse.code should equal(201)
|
||||
|
||||
val dynamicEntityId = (createResponse.body \ "dynamic_entity_id").extract[String]
|
||||
|
||||
When("We update the bank level dynamic entity")
|
||||
val updateRequest = (v6_0_0_Request / "management" / "banks" / bankId / "dynamic-entities" / dynamicEntityId).PUT <@(user1)
|
||||
val updateResponse = makePutRequest(updateRequest, write(updatedEntityV600))
|
||||
|
||||
Then("We should get a 200")
|
||||
updateResponse.code should equal(200)
|
||||
|
||||
And("Updated response should have snake_case fields")
|
||||
(updateResponse.body \ "entity_name").extract[String] should equal("FooBar")
|
||||
(updateResponse.body \ "bank_id").extract[String] should equal(bankId)
|
||||
|
||||
// Cleanup
|
||||
Entitlement.entitlement.vend.addEntitlement(bankId, resourceUser1.userId, CanDeleteBankLevelDynamicEntity.toString)
|
||||
val deleteRequest = (v4_0_0_Request / "management" / "banks" / bankId / "dynamic-entities" / dynamicEntityId).DELETE <@(user1)
|
||||
makeDeleteRequest(deleteRequest)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
feature("v6.0.0 My Dynamic Entities endpoints") {
|
||||
|
||||
scenario("GET My Dynamic Entities - without user credentials", ApiEndpoint7, VersionOfApi) {
|
||||
When("We make a GET request without user credentials")
|
||||
val request = (v6_0_0_Request / "my" / "dynamic-entities").GET
|
||||
val response = makeGetRequest(request)
|
||||
Then("We should get a 401")
|
||||
response.code should equal(401)
|
||||
}
|
||||
|
||||
scenario("GET and Update My Dynamic Entities with v6.0.0 format", ApiEndpoint7, ApiEndpoint8, VersionOfApi) {
|
||||
// First create a system entity with hasPersonalEntity = true
|
||||
Entitlement.entitlement.vend.addEntitlement("", resourceUser1.userId, CanCreateSystemLevelDynamicEntity.toString)
|
||||
|
||||
val createRequest = (v6_0_0_Request / "management" / "system-dynamic-entities").POST <@(user1)
|
||||
val createResponse = makePostRequest(createRequest, write(rightEntityV600))
|
||||
createResponse.code should equal(201)
|
||||
|
||||
val dynamicEntityId = (createResponse.body \ "dynamic_entity_id").extract[String]
|
||||
|
||||
When("We GET my dynamic entities")
|
||||
val getRequest = (v6_0_0_Request / "my" / "dynamic-entities").GET <@(user1)
|
||||
val getResponse = makeGetRequest(getRequest)
|
||||
|
||||
Then("We should get a 200")
|
||||
getResponse.code should equal(200)
|
||||
|
||||
val entitiesJson = getResponse.body \ "dynamic_entities"
|
||||
entitiesJson shouldBe a[JArray]
|
||||
|
||||
val entities = entitiesJson.asInstanceOf[JArray].arr
|
||||
entities.size should be >= 1
|
||||
|
||||
And("Response should use snake_case fields")
|
||||
val entity = entities.find(e => (e \ "entity_name").extract[String] == "FooBar").get
|
||||
(entity \ "dynamic_entity_id") shouldBe a[JString]
|
||||
(entity \ "entity_name").extract[String] should equal("FooBar")
|
||||
(entity \ "user_id").extract[String] should equal(resourceUser1.userId)
|
||||
(entity \ "has_personal_entity").extract[Boolean] should equal(true)
|
||||
|
||||
And("Definition should contain only the schema")
|
||||
val definition = entity \ "definition"
|
||||
(definition \ "description") shouldBe a[JString]
|
||||
(definition \ "FooBar") should equal(JNothing) // Should NOT have entity name as key
|
||||
|
||||
// Test Update My Dynamic Entity
|
||||
When("We update my dynamic entity")
|
||||
val updateRequest = (v6_0_0_Request / "my" / "dynamic-entities" / dynamicEntityId).PUT <@(user1)
|
||||
val updateResponse = makePutRequest(updateRequest, write(updatedEntityV600))
|
||||
|
||||
Then("We should get a 200")
|
||||
updateResponse.code should equal(200)
|
||||
|
||||
And("Updated response should use snake_case fields")
|
||||
(updateResponse.body \ "entity_name").extract[String] should equal("FooBar")
|
||||
(updateResponse.body \ "definition" \ "description").extract[String] should equal("Updated description of this entity.")
|
||||
|
||||
// Cleanup
|
||||
Entitlement.entitlement.vend.addEntitlement("", resourceUser1.userId, CanDeleteSystemLevelDynamicEntity.toString)
|
||||
val deleteRequest = (v4_0_0_Request / "management" / "system-dynamic-entities" / dynamicEntityId).DELETE <@(user1)
|
||||
makeDeleteRequest(deleteRequest)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
feature("v6.0.0 Available Personal Dynamic Entities discovery endpoint") {
|
||||
|
||||
scenario("GET Available Personal Dynamic Entities - without user credentials", ApiEndpoint9, VersionOfApi) {
|
||||
When("We make a GET request without user credentials")
|
||||
val request = (v6_0_0_Request / "personal-dynamic-entities" / "available").GET
|
||||
val response = makeGetRequest(request)
|
||||
Then("We should get a 401")
|
||||
response.code should equal(401)
|
||||
}
|
||||
|
||||
scenario("GET Available Personal Dynamic Entities returns only entities with hasPersonalEntity=true", ApiEndpoint9, VersionOfApi) {
|
||||
Entitlement.entitlement.vend.addEntitlement("", resourceUser1.userId, CanCreateSystemLevelDynamicEntity.toString)
|
||||
|
||||
// Create entity WITH hasPersonalEntity = true
|
||||
val createRequest1 = (v6_0_0_Request / "management" / "system-dynamic-entities").POST <@(user1)
|
||||
val response1 = makePostRequest(createRequest1, write(rightEntityV600))
|
||||
response1.code should equal(201)
|
||||
val entityId1 = (response1.body \ "dynamic_entity_id").extract[String]
|
||||
|
||||
// Create entity WITH hasPersonalEntity = false
|
||||
val createRequest2 = (v6_0_0_Request / "management" / "system-dynamic-entities").POST <@(user1)
|
||||
val response2 = makePostRequest(createRequest2, write(entityWithoutPersonalV600))
|
||||
response2.code should equal(201)
|
||||
val entityId2 = (response2.body \ "dynamic_entity_id").extract[String]
|
||||
|
||||
When("We GET available personal dynamic entities")
|
||||
val getRequest = (v6_0_0_Request / "personal-dynamic-entities" / "available").GET <@(user1)
|
||||
val getResponse = makeGetRequest(getRequest)
|
||||
|
||||
Then("We should get a 200")
|
||||
getResponse.code should equal(200)
|
||||
|
||||
val entities = (getResponse.body \ "dynamic_entities").asInstanceOf[JArray].arr
|
||||
|
||||
And("Response should contain only entities with has_personal_entity = true")
|
||||
val entityNames = entities.map(e => (e \ "entity_name").extract[String])
|
||||
entityNames should contain("FooBar")
|
||||
entityNames should not contain("SharedEntity")
|
||||
|
||||
And("All returned entities should have has_personal_entity = true")
|
||||
entities.foreach { entity =>
|
||||
(entity \ "has_personal_entity").extract[Boolean] should equal(true)
|
||||
}
|
||||
|
||||
And("Response should use snake_case fields")
|
||||
entities.foreach { entity =>
|
||||
(entity \ "dynamic_entity_id") shouldBe a[JString]
|
||||
(entity \ "entity_name") shouldBe a[JString]
|
||||
(entity \ "definition") shouldBe a[JObject]
|
||||
}
|
||||
|
||||
// Cleanup
|
||||
Entitlement.entitlement.vend.addEntitlement("", resourceUser1.userId, CanDeleteSystemLevelDynamicEntity.toString)
|
||||
val deleteRequest1 = (v4_0_0_Request / "management" / "system-dynamic-entities" / entityId1).DELETE <@(user1)
|
||||
makeDeleteRequest(deleteRequest1)
|
||||
val deleteRequest2 = (v4_0_0_Request / "management" / "system-dynamic-entities" / entityId2).DELETE <@(user1)
|
||||
makeDeleteRequest(deleteRequest2)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
feature("v6.0.0 Dynamic Entity definition field validation") {
|
||||
|
||||
scenario("Verify definition contains only schema, not entity name wrapper", ApiEndpoint1, VersionOfApi) {
|
||||
Entitlement.entitlement.vend.addEntitlement("", resourceUser1.userId, CanCreateSystemLevelDynamicEntity.toString)
|
||||
|
||||
val createRequest = (v6_0_0_Request / "management" / "system-dynamic-entities").POST <@(user1)
|
||||
val createResponse = makePostRequest(createRequest, write(rightEntityV600))
|
||||
createResponse.code should equal(201)
|
||||
|
||||
val dynamicEntityId = (createResponse.body \ "dynamic_entity_id").extract[String]
|
||||
val definition = createResponse.body \ "definition"
|
||||
|
||||
Then("Definition should contain schema fields directly")
|
||||
(definition \ "description") shouldBe a[JString]
|
||||
(definition \ "required") shouldBe a[JArray]
|
||||
(definition \ "properties") shouldBe a[JObject]
|
||||
|
||||
And("Definition should NOT contain the entity name as a nested key (old v4.0.0 format)")
|
||||
(definition \ "FooBar") should equal(JNothing)
|
||||
|
||||
And("Definition should NOT contain hasPersonalEntity (that's a separate top-level field)")
|
||||
(definition \ "hasPersonalEntity") should equal(JNothing)
|
||||
(definition \ "has_personal_entity") should equal(JNothing)
|
||||
|
||||
// Cleanup
|
||||
Entitlement.entitlement.vend.addEntitlement("", resourceUser1.userId, CanDeleteSystemLevelDynamicEntity.toString)
|
||||
val deleteRequest = (v4_0_0_Request / "management" / "system-dynamic-entities" / dynamicEntityId).DELETE <@(user1)
|
||||
makeDeleteRequest(deleteRequest)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user