Skip to content

Commit

Permalink
Topic/k1ch/ Introduce API - PUT:/clients/{client_id} (#99)
Browse files Browse the repository at this point in the history
* feat: topic/k1ch/ Introduce API PUT:/clients/{client_id}

* chore: topic/k1ch/api-put-clients/ Add API tests

* chore: topic/k1ch/api-put-clients/ set updated_at value
  • Loading branch information
k1ch authored Feb 23, 2024
1 parent 5d11fd0 commit f08f5d5
Show file tree
Hide file tree
Showing 6 changed files with 196 additions and 27 deletions.
59 changes: 38 additions & 21 deletions database/layer/admin-client.js
Original file line number Diff line number Diff line change
@@ -1,12 +1,7 @@
const { PGPool } = require('./pg_pool')
const pool = new PGPool()

module.exports = {
insertClient,
getClient,
updateClientByClientId,
deleteClientByClientId
}
const { usherDb } = require('./knex')
const { pgErrorHandler } = require('../utils/pgErrorHandler')

/**
*
Expand All @@ -19,7 +14,7 @@ module.exports = {
* @param {string} secret
* @returns Record with the newly created object
*/
async function insertClient (tenantName, clientId, name, description, secret) {
const insertClient = async (tenantName, clientId, name, description, secret) => {
try {
// validate tenant name and get tenant key
let sql = 'SELECT t.key from usher.tenants t WHERE t.name = $1'
Expand Down Expand Up @@ -56,11 +51,11 @@ async function insertClient (tenantName, clientId, name, description, secret) {
* @param {string} clientId The Client ID
* @returns client object
*/
async function getClient (clientId) {
const getClient = async (clientId) => {
const sql = 'SElECT c.client_id, c.name, c.description, c.secret FROM usher.clients c WHERE c.client_id = $1'
try {
const results = await pool.query(sql, [clientId])
if(results.rowCount === 0) {
if (results.rowCount === 0) {
throw new Error(`No results for client_id ${clientId}`)
}
return results.rows[0]
Expand All @@ -69,21 +64,36 @@ async function insertClient (tenantName, clientId, name, description, secret) {
}
}

async function updateClientByClientId (clientId, name, description, secret) {
const sql = 'UPDATE usher.clients SET name = $1, description = $2, secret = $3 WHERE client_id = $4'
/**
* Updates a client by client ID with the provided information.
*
* @param {string} clientId - The ID of the client to update.
* @param {Object} clientInfo - Object containing the updated client information.
* @param {string} clientInfo.client_id - The new client ID of the client.
* @param {string} clientInfo.name - The new name of the client.
* @param {string} clientInfo.description - The new description of the client.
* @param {string} clientInfo.secret - The new secret key of the client.
* @returns {Promise<Object>} The updated client object.
* @throws {Error} If an error occurs while updating the client.
*/
const updateClientByClientId = async (clientId, { client_id, name, description, secret }) => {
try {
const results = await pool.query(sql, [name, description, secret, clientId])
if (results.rowCount === 1) {
return 'Update successful'
} else {
return `Update failed: Client does not exist matching client_id ${clientId}`
}
} catch (error) {
return `Update failed: ${error.message}`
const [updatedClient] = await usherDb('clients')
.where({ client_id: clientId })
.update({
client_id,
name,
description,
secret,
updated_at: new Date(),
}).returning(['client_id', 'name', 'description', 'secret'])
return updatedClient
} catch (err) {
throw pgErrorHandler(err)
}
}

async function deleteClientByClientId (clientId) {
const deleteClientByClientId = async (clientId) => {
const sql = 'DELETE FROM usher.clients WHERE client_id = $1'
try {
const results = await pool.query(sql, [clientId])
Expand All @@ -96,3 +106,10 @@ async function deleteClientByClientId (clientId) {
return `Delete failed: ${error.message}`
}
}

module.exports = {
insertClient,
getClient,
updateClientByClientId,
deleteClientByClientId,
}
4 changes: 2 additions & 2 deletions database/test/db-insert.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -51,8 +51,8 @@ describe('Insert Update and Delete tests', function () {
})
it('Should update a single specified client', async function () {
try {
await postClients.updateClientByClientId('dummy_client', 'updated_clientname', 'updated_clientdescription', 'updated_secret')
await postClients.updateClientByClientId('dummy_client', 'Dummy Client', 'Dummy client for testing', 'secretsecretdonttell')
await postClients.updateClientByClientId('dummy_client', { client_id: 'dummy_client', name: 'updated_clientname', description: 'updated_clientdescription', secret: 'updated_secret' })
await postClients.updateClientByClientId('dummy_client', { client_id: 'dummy_client', name: 'Dummy Client', description: 'Dummy client for testing', secret: 'secretsecretdonttell' })
assert(true, 'Dummy client updated and reverted')
} catch (error) {
assert(false, error.message)
Expand Down
26 changes: 24 additions & 2 deletions server/src/api_endpoints/clients/index.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
const createError = require('http-errors')
const dbAdminRole = require('database/layer/admin-client')
const { checkClientExists } = require('./utils')

/**
* Client Admin function to create a Client
Expand All @@ -8,7 +9,7 @@ const dbAdminRole = require('database/layer/admin-client')
* @param {*} res
* @param {*} next
*/
const createClient = async (req, res, next) => {
const createClient = async (req, res, next) => {
const {
tenant_name: tenantName,
client_id: clientId,
Expand Down Expand Up @@ -65,8 +66,29 @@ const getClient = async (req, res, next) => {
}
}

/**
* HTTP Request handler
* Update a client by client_id
*
* @param {Object} req - The request object
* @param {Object} res - The response object to send 200 statusCode and updated client on success
* @param {Function} next - The next middleware function
* @returns {Promise<void>} - A promise that resolves to void when client is updated
*/
const updateClient = async (req, res, next) => {
try {
const { client_id: clientId } = req.params
await checkClientExists(clientId)
const updatedClient = await dbAdminRole.updateClientByClientId(clientId, req.body)
res.status(200).send(updatedClient)
} catch ({ httpStatusCode = 500, message }) {
return next(createError(httpStatusCode, { message }))
}
}

module.exports = {
createClient,
deleteClient,
getClient
getClient,
updateClient,
}
16 changes: 16 additions & 0 deletions server/src/api_endpoints/clients/utils.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
const dbAdminRole = require('database/layer/admin-client')

const checkClientExists = async (clientId) => {
try {
await dbAdminRole.getClient(clientId);
} catch {
throw {
httpStatusCode: 404,
message: 'Client does not exist!',
}
}
}

module.exports = {
checkClientExists,
}
86 changes: 84 additions & 2 deletions server/test/endpoint_clients.test.js
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
const { describe, it } = require('mocha')
const fetch = require('node-fetch')
const assert = require('assert')
const { getAdmin1IdPToken } = require('./lib/tokens')
const { getAdmin1IdPToken, getTestUser1IdPToken } = require('./lib/tokens')
const { getServerUrl } = require('./lib/urls')
const dbAdminRole = require('database/layer/admin-client')
const { usherDb } = require('../../database/layer/knex')

describe('Admin Clients Endpoint Test', () => {
let userAccessToken = ''
Expand Down Expand Up @@ -82,7 +83,6 @@ describe('Admin Clients Endpoint Test', () => {
assert.strictEqual(response.status, 200, 'Expected 200 response code')
assert.strictEqual(data.client_id, 'test-client1', 'Expected valid client id value')
})
it('Should allow client admin to get Client object')
})

describe('Delete Client', () => {
Expand All @@ -100,4 +100,86 @@ describe('Admin Clients Endpoint Test', () => {
assert.strictEqual(response.status, 404, 'Expected 404 response code')
})
})

describe('Update Client', () => {
let testClient
/**
* PUT /clients/{:client_id}
* HTTP request to Update a client by its client_id
*
* @param {string} clientId - The subject client id which needs to be updated
* @param {string} payload - The request body payload to update a client
* @param {Object} header - The request headers
* @returns {Promise<fetch.response>} - A Promise which resolves to fetch.response
*/
const updateClient = async (clientId, payload = { client_id: testClient?.client_id, name: testClient?.name }, header = requestHeaders) => {
return await fetch(`${url}/clients/${clientId}`, {
method: 'PUT',
headers: header,
body: JSON.stringify(payload)
})
}

beforeEach(async () => {
testClient = (await usherDb('clients').insert({
client_id: 'test_client_id',
name: 'test_client_name',
description: 'test_client_description',
secret: 'test_client_secret',
}).returning('*'))[0]
})

it('should return 200, update the client information', async () => {
const newClientInfo = {
client_id: 'updated_client_id',
name: 'updated_client_name',
description: 'updated_client_description',
secret: 'updated_client_secret',
}
const response = await updateClient(testClient.client_id, newClientInfo)
assert.equal(response.status, 200)
testClient.client_id = newClientInfo.client_id
const updatedClient = await response.json();
Object.entries(newClientInfo).forEach(([key, val]) => {
assert.equal(updatedClient[key], val)
})
})

it('should return 400, for invalid payloads', async () => {
const invalidRequestsResponses = await Promise.all([
updateClient(testClient.client_id, ''),
updateClient(testClient.client_id, {}),
updateClient(testClient.client_id, { name: 'test' }),
updateClient(testClient.client_id, { client_id: 'test' }),
updateClient(testClient.client_id, { client_id: 'test', name: 1 }),
updateClient(testClient.client_id, { client_id: 1, name: 'test' }),
])
invalidRequestsResponses.forEach(({ status }) => assert.equal(status, 400))
})

it('should return 401, unauthorized token', async () => {
const userAccessToken = await getTestUser1IdPToken()
const response = await updateClient(testClient.client_id, testClient,
{
...requestHeaders,
Authorization: `Bearer ${userAccessToken}`
})
assert.equal(response.status, 401)
})

it('should return 404, for invalid client_id', async () => {
const response = await updateClient('invalid_client_id')
assert.equal(response.status, 404)
})

it('should return 409, to update client_id if it already exist', async () => {
const { client_id: existingClientId } = await usherDb('clients').select('client_id').first()
const response = await updateClient(testClient.client_id, { ...testClient, client_id: existingClientId })
assert.equal(response.status, 409)
})

afterEach(async () => {
await usherDb('clients').where({ client_id: testClient.client_id }).del()
})
})
})
32 changes: 32 additions & 0 deletions server/the-usher-openapi-spec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -850,6 +850,38 @@ paths:
description: Successfully deleted the Client and data
404:
$ref: '#/components/responses/NotFound'
put:
'x-swagger-router-controller': 'clients/index'
operationId: updateClient
summary: Update a client
tags:
- Client Admin APIs
security:
- bearerAdminAuth: []
- bearerClientAdminAuth: []
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/Client'
responses:
200:
description: Return the details of updated client
content:
application/json:
schema:
$ref: '#/components/schemas/Client'
400:
$ref: '#/components/responses/BadRequest'
401:
$ref: '#/components/responses/Unauthorized'
404:
$ref: '#/components/responses/NotFound'
409:
$ref: '#/components/responses/Conflict'
500:
$ref: '#/components/responses/InternalError'

/clients/{client_id}/roles:
parameters:
Expand Down

0 comments on commit f08f5d5

Please sign in to comment.