Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Topic/k1ch/ Admin GET:/personas #91

Merged
merged 3 commits into from
Feb 16, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 31 additions & 0 deletions database/layer/admin-persona.js
Original file line number Diff line number Diff line change
Expand Up @@ -101,11 +101,42 @@ const deletePersonaByKey = async (personaKey) => {
}
}

/**
* Retrieve a list of personas based on filter criteria
*
* @param {Object} filterQuery - The filter query to apply (optional)
* @param {string} sort - The field to sort by (default: 'key')
* @param {string} order - The sort order ('asc' or 'desc', default: 'asc')
* @returns {Promise<Array>} - A promise that resolves to an array of personas with tenantname
* @throws {Error} - If there is an error during the retrieval process
*/
const getPersonas = async (filterQuery = {}, sort = 'key', order = 'asc') => {
try {
const tenantNameKey = 'tenantname'
const filters = Object.entries(filterQuery).reduce((acc, [key, val]) => {
if (key === tenantNameKey) {
acc.name = filterQuery[tenantNameKey]
} else {
acc[`personas.${key}`] = val
}
return acc
}, {})
return await usherDb('personas')
.select('personas.*', `tenants.name as ${tenantNameKey}`)
.join('tenants', 'personas.tenantkey', 'tenants.key')
.where(filters)
.orderBy(sort, order)
} catch (err) {
throw pgErrorHandler(err)
}
}

module.exports = {
insertPersona,
deletePersona,
updatePersona,
insertPersonaByTenantKey,
getPersona,
deletePersonaByKey,
getPersonas,
}
32 changes: 31 additions & 1 deletion database/test/db-admin-persona.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ describe('Admin persona view', () => {
})
})

describe('Test GET personas', () => {
describe('Test GET persona', () => {
const invalidPersonaKey = 0
it('Should return a valid persona', async () => {
const persona = await adminPersonas.getPersona(1)
Expand Down Expand Up @@ -116,4 +116,34 @@ describe('Admin persona view', () => {
await usherDb('personas').where({ key: testPersonaKey }).del()
})
})

describe('Test GET personas', () => {
it('Should return all the personas', async () => {
const { count: totalCount } = await usherDb('personas').count('*').first()
const personas = await adminPersonas.getPersonas()

assert.equal(personas.length, Number(totalCount))
assert.ok('tenantname' in personas[0])
})

it('Should return filtered personas when filterQuery is provided', async () => {
const { name: validTenantName } = await usherDb('tenants').select('name').first()
const filterQuery = { tenantname: validTenantName }
const personas = await adminPersonas.getPersonas(filterQuery)

personas.forEach((persona) => {
assert.equal(persona.tenantname, validTenantName)
})
})

it('Should return personas sorted by the specified field and order', async () => {
const sort = 'created_at'
const order = 'desc'
const personas = await adminPersonas.getPersonas({}, sort, order)

for (let i = 1; i < personas.length; i++) {
assert.ok(personas[i - 1][sort] >= personas[i][sort])
}
})
})
})
43 changes: 40 additions & 3 deletions server/src/api_endpoints/personas/persona.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,15 @@ const createError = require('http-errors')
const dbAdminPersona = require('database/layer/admin-persona')
const { checkPersonaExists } = require('./utils')

/**
* HTTP Request handler
* Create a persona
*
* @param {Object} req - The request object
* @param {Object} res - The response object to send 201 statusCode and the cerated persona on success
* @param {Function} next - The next middleware function
* @returns {Promise<void>} - A Promise that resolves to void when the persona is created
*/
const createPersona = async (req, res, next) => {
try {
const { tenant_key, sub_claim, user_context } = req.body
Expand All @@ -14,12 +23,12 @@ const createPersona = async (req, res, next) => {

/**
* HTTP Request handler
* Delete a persona by key and sends 204 statusCode on success
* Delete a persona by key
*
* @param {Object} req - The request object
* @param {Object} res - The response object
* @param {Object} res - The response object to send 204 statusCode on success
* @param {Function} next - The next middleware function
* @returns {Promise<void>} - A promise that resolves to void
* @returns {Promise<void>} - A promise that resolves to void when persona is deleted
*/
const deletePersona = async (req, res, next) => {
try {
Expand All @@ -32,7 +41,35 @@ const deletePersona = async (req, res, next) => {
}
}

/**
* HTTP Request handler
* Get personas with optional filtering, sorting, and ordering
*
* @param {Object} req - The request object
* @param {Object} res - The response object to send 200 statusCode and a list of personas
* @param {Function} next - The callback function for the next middleware
* @returns {Promise<void>} - A promise that resolves to void when personas are retrieved
*/
const getPersonas = async (req, res, next) => {
try {
const { sort, order } = req.query
const allowedFilterParameters = ['key', 'tenantkey', 'tenantname', 'sub_claim', 'user_context']
const filters = allowedFilterParameters.reduce((acc, filter) => {
const filterValue = req.query[filter]
if (filterValue) {
acc[filter] = filterValue
}
return acc
}, {})
const personas = await dbAdminPersona.getPersonas(filters, sort, order)
res.status(200).send(personas);
} catch ({ httpStatusCode = 500, message }) {
return next(createError(httpStatusCode, { message }))
}
}

module.exports = {
createPersona,
deletePersona,
getPersonas,
}
94 changes: 94 additions & 0 deletions server/test/endpoint_admin_personas.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,14 @@ describe('Admin Personas', () => {
let testPersonaKey
let validTenantKey
const invalidPersonaKey = 999999
/**
* DELETE /personas/{:key}
* HTTP request to delete a persona by its persona_key
*
* @param {string} personaKey - The key of the persona to be deleted
* @param {Object} header - The request headers
* @returns {Promise<fetch.response>} - A Promise which resolves to fetch.response
*/
const deletePersona = async (personaKey = testPersonaKey, header = requestHeaders) => {
return await fetch(`${url}/personas/${personaKey}`, {
method: 'DELETE',
Expand Down Expand Up @@ -118,4 +126,90 @@ describe('Admin Personas', () => {
await usherDb('personas').where({ key: testPersonaKey }).del()
})
})

describe('GET:/personas', () => {
/**
* GET /personas
* HTTP request to retrieve a list of personas
*
* @param {string} query - The query params to be added to the URL (E.g. ?tenantname=value1&sub_claim=value2&sort=sub_claim&order=desc)
* @param {Object} header - The request headers
* @returns {Promise<fetch.response>} - A Promise which resolves to fetch.response
*/
const getPersonas = async (query = '', header = requestHeaders) => {
return await fetch(`${url}/personas${query}`, {
method: 'GET',
headers: header,
})
}

it('should return 200, return all the personas', async () => {
const { count: totalCount } = await usherDb('personas').count('*').first()
const response = await getPersonas()
assert.equal(response.status, 200)
const personas = await response.json()
assert.equal(personas.length, Number(totalCount))
})

it('should return 200, return all the personas for a tenant', async () => {
const { name: validTenantName, key: tenantkey } = await usherDb('tenants').select('*').first()
const { count: totalCount } = await usherDb('personas').where({ tenantkey }).count('*').first()
const response = await getPersonas(`?tenantname=${validTenantName}`)
assert.equal(response.status, 200)
const personas = await response.json()
assert.equal(personas.length, Number(totalCount))
assert.equal(personas[0]['tenantname'], validTenantName)
})

it('should return 200, return a persona with two filter parameters', async () => {
const validPersona = await usherDb('personas').select('*').first()
const { tenantkey, sub_claim } = validPersona
const response = await getPersonas(`?tennatkey=${tenantkey}&sub_claim=${sub_claim}`)
assert.equal(response.status, 200)
const personas = await response.json()
assert.equal(personas.length, 1)
assert.equal(personas[0]['tenantkey'], tenantkey)
assert.equal(personas[0]['sub_claim'], sub_claim)
})

it('should return 200, return empty array for invalid tennatname', async () => {
const response = await getPersonas('?tenantname=invalid')
assert.equal(response.status, 200)
const personas = await response.json()
assert.equal(personas.length, 0)
})

it('should return 200, return a sorted list based on created_at in descending order ', async () => {
const sort = 'created_at'
const response = await getPersonas(`?sort=${sort}&order=desc`)
assert.equal(response.status, 200)
const personas = await response.json()
for (let i = 1; i < personas.length; i++) {
assert.ok(personas[i - 1][sort] >= personas[i][sort])
}
})

it('should return 400, due to invalid query params', async () => {
const responses = await Promise.all(
[
getPersonas('?key=NaN'),
getPersonas('?sort=key,'),
getPersonas('?sort=key&order'),
getPersonas('?sort=key&order=not_asc_desc'),

]
)
assert.equal(responses.every(({ status }) => status === 400), true)
})

it('should return 401, unauthorized token', async () => {
const userAccessToken = await getTestUser1IdPToken()
const response = await getPersonas('',
{
...requestHeaders,
Authorization: `Bearer ${userAccessToken}`
})
assert.equal(response.status, 401)
})
})
})
78 changes: 78 additions & 0 deletions server/the-usher-openapi-spec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -429,6 +429,84 @@ paths:
$ref: '#/components/responses/NotFound'

/personas:
get:
'x-swagger-router-controller': 'personas/persona'
operationId: getPersonas
summary: Retrieve a list of personas
parameters:
- name: key
in: query
description: Filter by key (exact match)
schema:
type: number
example: 10
- name: tenantkey
in: query
description: Filter by tenantkey (exact match)
schema:
type: number
example: 10
- name: tenantname
in: query
description: Filter by tenantname (exact match)
schema:
type: string
example: DMGT
- name: sub_claim
in: query
description: Filter by sub_claim (exact match)
schema:
type: string
example: auth0|test-persona
- name: user_context
in: query
description: Filter by user_context (exact match)
schema:
type: string
- name: sort
in: query
description: Sort the results by a field
schema:
type: string
example: sub_claim
enum:
[
key,
tenantkey,
tenantname,
sub_claim,
user_context,
created_at,
updated_at,
]
- name: order
in: query
description: Specify the sort order (asc or desc)
schema:
type: string
enum: [asc, desc]
tags:
- Admin APIs
security:
- bearerAdminAuth: []
responses:
200:
description: Returns a list of personas
content:
application/json:
schema:
type: array
items:
$ref: "#/components/schemas/Persona"
properties:
tenantname:
type: string
400:
$ref: '#/components/responses/BadRequest'
401:
$ref: '#/components/responses/Unauthorized'
500:
$ref: '#/components/responses/InternalError'
post:
'x-swagger-router-controller': 'personas/persona'
summary: Create a single persona
Expand Down
Loading