-
Notifications
You must be signed in to change notification settings - Fork 6
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Topics/k1ch/Admin APIs - POST:/personas (#79)
* feat: topics/k1ch/ introduce POST/personas * feat: topics/k1ch/POST-personas/error handling * chore: topics/k1ch/POST-personas/db-layer and API tests * chore: topics/k1ch/POST-personas/code style clean * chore: topics/k1ch/POST-personas/minor updates
- Loading branch information
Showing
11 changed files
with
357 additions
and
22 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,55 @@ | ||
const PgErrorCodes = { | ||
// Class 08 - Connection Exception | ||
ConnectionException: '08000', | ||
ConnectionDoesNotExist: '08003', | ||
ConnectionFailure: '08006', | ||
|
||
// Class 22 - Data Exception | ||
DataException: '22000', | ||
NullValueNotAllowed: '22004', | ||
NumericValueOutOfRange: '22003', | ||
InvalidTextRepresentation: '22P02', | ||
|
||
// Class 23 - Integrity Constraint Violation | ||
IntegrityConstraintViolation: '23000', | ||
NotNullViolation: '23502', | ||
ForeignKeyViolation: '23503', | ||
UniqueViolation: '23505', | ||
CheckViolation: '23514', | ||
|
||
// Class 25 - Invalid Transaction State | ||
InvalidTransactionState: '25000', | ||
ActiveSQLTransaction: '25001', | ||
InFailedSQLTransaction: '25P02', | ||
|
||
// Class 28 - Invalid Authorization Specification | ||
InvalidAuthorizationSpecification: '28000', | ||
InvalidPassword: '28P01', | ||
|
||
// Class 40 - Transaction Rollback | ||
TransactionRollback: '40000', | ||
SerializationFailure: '40001', | ||
DeadlockDetected: '40P01', | ||
|
||
// Class 42 - Syntax Error or Access Rule Violation | ||
SyntaxErrorOrAccessRuleViolation: '42000', | ||
SyntaxError: '42601', | ||
UndefinedColumn: '42703', | ||
UndefinedTable: '42P01', | ||
DuplicateColumn: '42701', | ||
DuplicateTable: '42P07', | ||
|
||
// Class 53 - Insufficient Resources | ||
InsufficientResources: '53000', | ||
DiskFull: '53100', | ||
OutOfMemory: '53200', | ||
TooManyConnections: '53300', | ||
|
||
// Class 54 - Program Limit Exceeded | ||
ProgramLimitExceeded: '54000', | ||
StatementTooComplex: '54001', | ||
} | ||
|
||
module.exports = { | ||
PgErrorCodes, | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
const knex = require('knex'); | ||
const knexDbConfig = require('../knexfile'); | ||
const usherDb = knex(knexDbConfig); | ||
|
||
module.exports = { usherDb } |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,45 +1,53 @@ | ||
const { describe, it } = require('mocha') | ||
const assert = require('assert') | ||
const postPersonas = require('../layer/admin-persona.js') | ||
const { usherDb } = require('../layer/knex') | ||
|
||
describe('Admin persona view', function () { | ||
describe('Test INSERT personas', function () { | ||
it('Should insert persona without an exception', async function () { | ||
describe('Admin persona view', () => { | ||
describe('Test INSERT personas', () => { | ||
it('Should insert persona without an exception', async () => { | ||
const insertResult = await postPersonas.insertPersona('test-tenant1', 'http://idp.dmgt.com.mock.localhost:3002/', '[email protected]', '') | ||
assert.strictEqual(insertResult, 'Insert successful') | ||
await postPersonas.deletePersona('test-tenant1', 'http://idp.dmgt.com.mock.localhost:3002/', '[email protected]', '') | ||
}) | ||
it('Should fail to insert for a nonexistent tenant', async function () { | ||
it('Should fail to insert for a nonexistent tenant', async () => { | ||
const insertResult = await postPersonas.insertPersona('test-tenant1 Non-existent', 'http://idp.dmgt.com.mock.localhost:3002/', '[email protected]', '') | ||
assert.strictEqual(insertResult, 'Insert failed: Tenant does not exist matching tenantname test-tenant1 Non-existent iss_claim http://idp.dmgt.com.mock.localhost:3002/') | ||
}) | ||
it('Should fail to insert duplicate tenant/persona combination - check tenantname', async function () { | ||
it('Should fail to insert duplicate tenant/persona combination - check tenantname', async () => { | ||
await postPersonas.insertPersona('test-tenant1', 'http://idp.dmgt.com.mock.localhost:3002/', '[email protected]', '') | ||
const result = await postPersonas.insertPersona('test-tenant1', 'http://idp.dmgt.com.mock.localhost:3002/', '[email protected]', '') | ||
assert.strictEqual(result, 'Insert failed: A persona (sub_claim = [email protected]; user_context = ) already exists on tenantname test-tenant1 iss_claim http://idp.dmgt.com.mock.localhost:3002/') | ||
await postPersonas.deletePersona('test-tenant1', 'http://idp.dmgt.com.mock.localhost:3002/', '[email protected]', '') | ||
}) | ||
it('Should insert persona by tenant key without an exception', async () => { | ||
const subClaim = '[email protected]' | ||
const [tenant] = await usherDb('tenants').select('*').limit(1) | ||
const persona = await postPersonas.insertPersonaByTenantKey(tenant.key, subClaim) | ||
assert.strictEqual(persona.sub_claim, subClaim) | ||
await usherDb('personas').where({ key: persona.key }).del() | ||
}) | ||
}) | ||
|
||
describe('Test UPDATE personas', function () { | ||
it('Should update persona without an exception by tenantname', async function () { | ||
describe('Test UPDATE personas', () => { | ||
it('Should update persona without an exception by tenantname', async () => { | ||
await postPersonas.insertPersona('test-tenant1', 'http://idp.dmgt.com.mock.localhost:3002/', '[email protected]', '') | ||
const resultTenantname = await postPersonas.updatePersona('test-tenant1', 'http://idp.dmgt.com.mock.localhost:3002/', '[email protected]', '[email protected]', '', '') | ||
assert.strictEqual(resultTenantname, 'Update successful') | ||
await postPersonas.deletePersona('test-tenant1', 'http://idp.dmgt.com.mock.localhost:3002/', '[email protected]', '') | ||
}) | ||
it('Should fail to update for a nonexistent tenant', async function () { | ||
it('Should fail to update for a nonexistent tenant', async () => { | ||
const resultTenantname = await postPersonas.updatePersona('test-tenant1 Non-existent', 'http://idp.dmgt.com.mock.localhost:3002/', 'auth0|test-persona2-REPLACE', 'should_not_replace_sub_claim', '', '') | ||
assert.strictEqual(resultTenantname, 'Update failed: A persona (sub_claim = auth0|test-persona2-REPLACE; user_context = ) does not exist on tenantname test-tenant1 Non-existent iss_claim http://idp.dmgt.com.mock.localhost:3002/') | ||
}) | ||
it('Should fail to update for a nonexistent persona', async function () { | ||
it('Should fail to update for a nonexistent persona', async () => { | ||
const resultTenantname = await postPersonas.updatePersona('test-tenant1', 'http://idp.dmgt.com.mock.localhost:3002/', '[email protected]', 'should_not_replace_sub_claim', '', '') | ||
assert.strictEqual(resultTenantname, 'Update failed: A persona (sub_claim = [email protected]; user_context = ) does not exist on tenantname test-tenant1 iss_claim http://idp.dmgt.com.mock.localhost:3002/') | ||
}) | ||
}) | ||
|
||
describe('Test DELETE personas', function () { | ||
it('Should fail to delete a persona not linked to a tenant', async function () { | ||
describe('Test DELETE personas', () => { | ||
it('Should fail to delete a persona not linked to a tenant', async () => { | ||
const resultDelete = await postPersonas.deletePersona('test-tenant1', 'http://idp.dmgt.com.mock.localhost:3002/', '[email protected]', '') | ||
assert.strictEqual(resultDelete, 'Delete failed: A persona (sub_claim = [email protected]; user_context = ) does not exist on tenantname test-tenant1 iss_claim http://idp.dmgt.com.mock.localhost:3002/') | ||
}) | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,82 @@ | ||
const { PgErrorCodes } = require('../constant/PgErrorCodes') | ||
|
||
/** | ||
* Handles database errors into a generic message and appropriate http status code | ||
* @param pgDbError The error thrown by the database. | ||
* @returns {message: text, httpStatusCode: number} | ||
*/ | ||
const pgErrorHandler = (pgDbError) => { | ||
const error = {} | ||
switch (pgDbError.code) { | ||
case PgErrorCodes.UniqueViolation: | ||
error.message = 'The operation would result in duplicate resources!' | ||
error.httpStatusCode = 409 | ||
break | ||
|
||
case PgErrorCodes.CheckViolation: | ||
error.message = 'The operation would violate a check constraint!' | ||
error.httpStatusCode = 400 | ||
break | ||
|
||
case PgErrorCodes.NotNullViolation: | ||
error.message = 'A required value is missing!' | ||
error.httpStatusCode = 400 | ||
break | ||
|
||
case PgErrorCodes.ForeignKeyViolation: | ||
error.message = 'Referenced resource is invalid!' | ||
error.httpStatusCode = 400 | ||
break | ||
|
||
case PgErrorCodes.InvalidTextRepresentation: | ||
error.message = 'The provided data format is invalid!' | ||
error.httpStatusCode = 400 | ||
break | ||
|
||
case PgErrorCodes.SerializationFailure: | ||
error.message = 'Internal DB Error: A transaction serialization error occurred!' | ||
error.httpStatusCode = 500 | ||
break | ||
|
||
case PgErrorCodes.DeadlockDetected: | ||
error.message = 'Internal DB Error: The operation was halted due to a potential deadlock!' | ||
error.httpStatusCode = 500 | ||
break | ||
|
||
case PgErrorCodes.SyntaxError: | ||
error.message = 'Internal DB Error: There is a syntax error in the provided SQL or data!' | ||
error.httpStatusCode = 500 | ||
break | ||
|
||
case PgErrorCodes.UndefinedTable: | ||
error.message = 'Internal DB Error: The table or view you are trying to access does not exist!' | ||
error.httpStatusCode = 500 | ||
break | ||
|
||
case PgErrorCodes.DiskFull: | ||
error.message = 'Internal DB Error: The operation failed due to insufficient disk space!' | ||
error.httpStatusCode = 500 | ||
break | ||
|
||
case PgErrorCodes.OutOfMemory: | ||
error.message = 'Internal DB Error: The system ran out of memory!' | ||
error.httpStatusCode = 500 | ||
break | ||
|
||
case PgErrorCodes.TooManyConnections: | ||
error.message = 'Internal DB Error: There are too many connections to the database!' | ||
error.httpStatusCode = 500 | ||
break | ||
|
||
default: | ||
error.message = `Unexpected Error: ${error?.message}!` | ||
error.httpStatusCode = 500 | ||
break | ||
} | ||
|
||
return error | ||
} | ||
|
||
module.exports = { | ||
pgErrorHandler, | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
const createError = require('http-errors') | ||
const dbAdminPersona = require('database/layer/admin-persona') | ||
|
||
const createPersona = async (req, res, next) => { | ||
try { | ||
const { tenant_key, sub_claim, user_context } = req.body | ||
const persona = await dbAdminPersona.insertPersonaByTenantKey(tenant_key, sub_claim, user_context) | ||
res.status(201).send(persona) | ||
} catch ({ httpStatusCode = 500, message }) { | ||
return next(createError(httpStatusCode, { message })) | ||
} | ||
} | ||
|
||
module.exports = { | ||
createPersona, | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,67 @@ | ||
const { describe, it, before, afterEach } = require('mocha') | ||
const fetch = require('node-fetch') | ||
const assert = require('assert') | ||
|
||
const { getAdmin1IdPToken } = require('./lib/tokens') | ||
const { getServerUrl } = require('./lib/urls') | ||
const { usherDb } = require('../../database/layer/knex') | ||
|
||
describe('Admin Personas', () => { | ||
let requestHeaders | ||
const url = `${getServerUrl()}` | ||
|
||
before(async () => { | ||
const userAccessToken = await getAdmin1IdPToken() | ||
requestHeaders = { | ||
'Content-Type': 'application/json', | ||
Authorization: `Bearer ${userAccessToken}`, | ||
} | ||
}) | ||
|
||
describe('POST:/personas', () => { | ||
const validPersonaPayload = { | ||
'sub_claim': '[email protected]', | ||
'tenant_key': 1, | ||
} | ||
|
||
it('should return 201 - create a persona', async () => { | ||
const response = await fetch(`${url}/personas`, { | ||
method: 'POST', | ||
headers: requestHeaders, | ||
body: JSON.stringify(validPersonaPayload) | ||
}) | ||
assert.strictEqual(response.status, 201) | ||
const persona = await response.json() | ||
assert.strictEqual(persona.sub_claim, validPersonaPayload.sub_claim) | ||
}) | ||
|
||
it('should return 400 - fail to create a persona due to invalid tenant', async () => { | ||
const response = await fetch(`${url}/personas`, { | ||
method: 'POST', | ||
headers: requestHeaders, | ||
body: JSON.stringify({ | ||
...validPersonaPayload, | ||
'tenant_key': 1000, | ||
}) | ||
}) | ||
assert.strictEqual(response.status, 400) | ||
}) | ||
|
||
it('should return 409 - fail to create persona due to conflict', async () => { | ||
const { sub_claim, tenant_key: tenantkey } = validPersonaPayload | ||
await usherDb('personas').insert({ sub_claim, tenantkey }) | ||
const response = await fetch(`${url}/personas`, { | ||
method: 'POST', | ||
headers: requestHeaders, | ||
body: JSON.stringify(validPersonaPayload) | ||
}) | ||
assert.strictEqual(response.status, 409) | ||
}) | ||
|
||
afterEach(async () => { | ||
try { | ||
await usherDb('personas').where({ sub_claim: validPersonaPayload.sub_claim }).del() | ||
} catch { } | ||
}) | ||
}) | ||
}) |
Oops, something went wrong.