v6.0.0 of dynmaic endpoints with improved json

This commit is contained in:
simonredfern 2026-01-20 16:39:18 +01:00
parent ceba49c0ea
commit bfa3917ce1
3 changed files with 1158 additions and 8 deletions

View File

@ -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,

View File

@ -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
)
}
}

View 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)
}
}
}