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

Added:Control Certer Backend APIs #45

Merged
merged 15 commits into from
Apr 3, 2024
3 changes: 2 additions & 1 deletion .env.sample
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,5 @@ SERVER_PORT=3001
TWILIO_ACCOUNT_SID=
TWILIO_AUTH_TOKEN=
TWILIO_NUMBER=
TEST_RECEPIENT_NUMBER=
TEST_RECEPIENT_NUMBER=
STRAPI_TOURISM_TOKEN=
12 changes: 10 additions & 2 deletions config/openai.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,14 @@
{ "role": "system", "content": "A typical order flow should be search > select > init > confirm."},
{ "role": "system", "content": "Use the response of search from assistant to select items from the list of items provided by the assistant."},
{ "role": "system", "content": "Use the response of search request from assistant for filling transaction_id, bpp_id, bpp_uri in the context of all calls except `search`."},
{ "role": "system", "content": "For `select`, `init`, `confirm`, you must use the item `id` as part of the payload for selected item instead of name or any other key."}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is the prompt changed for line #14?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This needs to be fixed.

]
{ "role": "system", "content": "Use the response from assistant to select items from the list of items provided by the assistant."}

],
"PRESETS" : {
"bap_id": "mit-ps-bap.becknprotocol.io",
"bap_uri": "https://mit-ps-bap.becknprotocol.io",
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should not be here under openai.json.

One that things like bap_id, bap_uri etc are now part of registry.json
Strapi url is not openai related config, it should be under a different file like config.json or constants.js file that you have created.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not done.

"version": "1.1.0",
"base_url": "https://mit-ps-bap-client.becknprotocol.io",
"TOURISM_STRAPI_URL": "https://mit-bpp-tourism.becknprotocol.io/api"
}
}
91 changes: 91 additions & 0 deletions controllers/ControlCenter.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
import Actions from '../services/Actions.js'
import { readFileSync } from 'fs';
import logger from '../utils/logger.js'
import {
ITEM_ID,
ITEM_NAME,
CAT_ATTR_TAG_RELATIONS,
NEW_CATALOG_AVAILABLE,
TRIGGER_BLIZZARD_MESSAGE,
CANCEL_BOOKING_MESSAGE
} from '../utils/constants.js'
const config = JSON.parse(readFileSync('./config/openai.json'))

const action = new Actions()
const STRAPI_TOURISM_TOKEN = process.env.STRAPI_TOURISM_TOKEN || ''
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No need to re-decalre STRAPI_TOURISM_TOKEN or STRAPI_TOURISM_TOKEN as they can be used directly from the environment variables.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

const TWILIO_RECEPIENT_NUMBER = process.env.TEST_RECEPIENT_NUMBER
export const cancelBooking = async (req, res) => {
try {
const { orderId } = req.body
if(!orderId){
return res.status(500).json({message:"Order Id is Required", status:false})
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

500 is for internal server error, you should use 400 for a bad request , typically used for missing data.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

}
const validOrderId = await action.call_api(`${config.PRESETS.TOURISM_STRAPI_URL}/orders/${orderId}`,'GET',{},{ Authorization: `Bearer ${STRAPI_TOURISM_TOKEN}`})
if(!validOrderId.status){
return res.send({ message: `Invalid Order Id`, status:false })
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Status should be 400, its sending 200 when the order id is invalid. Isn't it a bad request?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

}
const messageBody = CANCEL_BOOKING_MESSAGE;
const getOrderAddressDetails = await action.call_api(`${config.PRESETS.TOURISM_STRAPI_URL}/order-addresses?order_id=${orderId}`,'GET',{},{ Authorization: `Bearer ${STRAPI_TOURISM_TOKEN}`})

const getOrderFulfillmentDetails = await action.call_api(`${config.PRESETS.TOURISM_STRAPI_URL}/order-fulfillments?order_id=${orderId}`,'GET',{},{ Authorization: `Bearer ${STRAPI_TOURISM_TOKEN}`})
if (getOrderFulfillmentDetails.data.data.length) {
await action.call_api(`${config.PRESETS.TOURISM_STRAPI_URL}/order-fulfillments/${getOrderFulfillmentDetails.data.data[0].id}`,'PUT',{
data: {
state_code: 'CANCELLED',
state_value: 'CANCELLED BY HOTEL',
},
},{ Authorization: `Bearer ${STRAPI_TOURISM_TOKEN}`})
let statusMessage = "";

if(getOrderAddressDetails.data.data[0].attributes.phone){
statusMessage = (await action.send_message(`+91${getOrderAddressDetails.data.data[0].attributes.phone}`, messageBody)).status.status
}
else{
statusMessage = (await action.send_message(TWILIO_RECEPIENT_NUMBER, messageBody)).status.status
}
return res.send({ message: `Notification ${statusMessage}` })
}

return res.send({ message: 'Cancel Booking Failed' })
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

status code should not be 200.

} catch (error) {
logger.error(error.message)
return res.send({ message: error.message })
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

status should not be 200.

}
}

export const updateCatalog = async (req, res) => {
try {
const { userNo = TWILIO_RECEPIENT_NUMBER } = req.body;
const messageBody = NEW_CATALOG_AVAILABLE;
await action.call_api(`${config.PRESETS.TOURISM_STRAPI_URL}/items/${ITEM_ID}`,'PUT',{
data: {
name: ITEM_NAME,
cat_attr_tag_relations: CAT_ATTR_TAG_RELATIONS,
},
},{ Authorization: `Bearer ${STRAPI_TOURISM_TOKEN}`})
const notifyResponse = await action.send_message(userNo, messageBody)
if(notifyResponse.status.status === "failed"){
throw new Error('Notification Failed')
}
return res.send({ message: 'Catalog Updated' })
} catch (error) {
logger.error(error.message)
return res.send({ message: error.message })
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Status code should not be 200.

}
}


export const notify = async (req, res) => {
try {
const { userNo = TWILIO_RECEPIENT_NUMBER } = req.body;
const messageBody = TRIGGER_BLIZZARD_MESSAGE;
const sendWhatsappNotificationResponse = await action.send_message(
userNo,
messageBody
)
return res.send(sendWhatsappNotificationResponse)
} catch (error) {
logger.error(error.message)
return res.send({ message: error.message })
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Status code should not be 200.

}
}
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
"body-parser": "^1.20.2",
"chai": "^5.0.0",
"config": "^3.3.11",
"cors": "^2.8.5",
"dotenv": "^16.3.1",
"express": "^4.18.2",
"js-yaml": "^4.1.0",
Expand Down
19 changes: 13 additions & 6 deletions server.js
Original file line number Diff line number Diff line change
@@ -1,13 +1,18 @@
import dotenv from 'dotenv'
import cors from 'cors'
dotenv.config()
import express from 'express'
import bodyParser from 'body-parser'
import logger from './utils/logger.js'
import messageController from './controllers/Bot.js'
import DBService from './services/DBService.js'

import {
cancelBooking,
updateCatalog,
notify
} from './controllers/ControlCenter.js'
const app = express()

app.use(cors())
// parse application/x-www-form-urlencoded
app.use(bodyParser.urlencoded({ extended: false }))

Expand All @@ -16,11 +21,13 @@ app.use(bodyParser.json())

// Define endpoints here
// app.post('/act', actions.act)
app.post('/webhook', messageController.process_text)

app.post('/webhook', messageController.process_wa_webhook)
app.post('/notify', notify)
app.post('/cancel-booking', cancelBooking)
app.post('/update-catalog', updateCatalog)
// Reset all sessions
const db = new DBService();
await db.clear_all_sessions();
const db = new DBService()
await db.clear_all_sessions()

// Start the Express server
app.listen(process.env.SERVER_PORT, () => {
Expand Down
10 changes: 5 additions & 5 deletions services/Actions.js
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ class Actions {
cookies: response.headers['set-cookie'],
}
logger.info(`API call was successful: , response.status`)
logger.info(JSON.stringify(response.data, null, 2))
// logger.info(JSON.stringify(response.data, null, 2))
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remove the comment, unnecessary change.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

} catch (error) {
logger.error(error)

Expand Down Expand Up @@ -106,14 +106,14 @@ class Actions {

async send_message(recipient, message) {
try {

const response = await client.messages.create({
const data = await client.messages.create({
body: message,
from: `whatsapp:${twilioNumber}`,
to: recipient.includes('whatsapp:') ? recipient : `whatsapp:${recipient}`,
})
logger.info(`Message sent: ${JSON.stringify(response)}`)
return true;
const status = await client.messages(data.sid).fetch()

return { data, status }
} catch (error) {
logger.error(`Error sending message: ${error.message}`)
return false;
Expand Down
78 changes: 78 additions & 0 deletions tests/unit/controllers/controlCenter.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
import { describe, it} from 'mocha'
import app from '../../../server.js'
import request from 'supertest'
import * as chai from 'chai'
const expect = chai.expect


describe('API tests for /notify endpoint for an end to end Notify Request', () => {
it('Should test unsuccess response for invalid whatsapp number.', async () => {
const response = await request(app).post('/notify').send({
"userNo":"+919123456789"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This looks like a valid number, it should be something like "INVALID_NUMBER", a string is invalid number and it will defiitely faill. Test cases should be written in such a way that they always do what they are supposed to do. They should not be flaky (which works sometimes and dont work sometimes)

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

})
expect(response._body.status.status).equal('failed')
})

it('Should test success response for no whatsapp number provided in the payload and will sent to TEST_RECEPIENT_NUMBER', async () => {
const response = await request(app).post('/notify').send({

})
expect(response._body.status.status).to.not.equal('failed')
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If its succes case, why are we testing for failure?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

})

it('Should test success response for valid whatsapp number', async () => {
const response = await request(app).post('/notify').send({
"userNo":process.env.TEST_RECEPIENT_NUMBER
})
expect(response._body.status.status).to.not.equal('failed')
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If its a sucess, response, why is the status being test against failed?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

})


})



describe('API tests for /cancel-booking endpoint for an end to end Notify Message', () => {
it('Should test unsuccess response for invalid order Id.', async () => {
const response = await request(app).post('/cancel-booking').send({
"orderId":"Abcd"
})
expect(response._body.status).equal(false)
})

it('Should test unsuccess response for no order Id.', async () => {
const response = await request(app).post('/cancel-booking').send({})
expect(response._body.status).equal(false)
})


it('Should test success response for valid order Id.', async () => {
const response = await request(app).post('/cancel-booking').send({
"orderId":"1"
})
expect(response._body.message).equal('Notification delivered')
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should check for status also.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

})


})

describe('API tests for /update-catalog endpoint for an end to end Notify Message', () => {
mayurvir marked this conversation as resolved.
Show resolved Hide resolved
it('Should test success response for invalid whatsapp No.', async () => {
const response = await request(app).post('/update-catalog').send({
"userNo":"+919123456789"
})
expect(response._body.message).equal('Notification Failed')
})

it('Should test success response for no whatsapp number provided in the payload and will sent to TEST_RECEPIENT_NUMBER', async () => {
const response = await request(app).post('/update-catalog').send({})
expect(response._body.message).equal('Catalog Updated')
})

it('Should test success response for valid whatsapp number', async () => {
const response = await request(app).post('/update-catalog').send({
"userNo":process.env.TEST_RECEPIENT_NUMBER
})
expect(response._body.message).equal('Catalog Updated')
})
})
8 changes: 8 additions & 0 deletions utils/constants.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
export const ITEM_ID = '4'
export const ITEM_NAME = 'Ticket Pass-Mueseum'

export const CAT_ATTR_TAG_RELATIONS = [2, 3, 4, 5]
export const TRIGGER_BLIZZARD_MESSAGE = "Hey, Triggering a Blizzard";
export const CANCEL_BOOKING_MESSAGE = `Dear Guest,\n\nApologies, but your hotel booking with us has been canceled due to unforeseen circumstances. \nWe understand the inconvenience and are here to assist you with any alternative arrangements needed. \n\nPlease contact us for further assistance.`;
export const NEW_CATALOG_AVAILABLE = `Dear Guest,\n\n Checkout this new place to visit.`

Loading