update: separate stateful and stateless scenarios

This commit is contained in:
Mohamed Sohail 2022-03-25 13:14:35 +03:00
parent 5d7a85b729
commit 97098482af
Signed by: kamikazechaser
GPG Key ID: 7DD45520C01CD85D
9 changed files with 185 additions and 33 deletions

View File

@ -1,6 +1,6 @@
# cic-ussd-e2e # cic-ussd-e2e
> Mocking user interaction with cic-ussd Bunch of test scripts to automate traversing USSD menu's with little human interaction and as close replication to actual USSD usage including AT style reqests and waiting times.
## Setup ## Setup
@ -15,19 +15,39 @@
- Node.js - Node.js
- Install dependencies with `$ npm i` - Install dependencies with `$ npm i`
- Set global config in `config.js`
## Scenarios
| Test | Type | Description |
| -------------- | --------- | ---------------------------------------------- |
| initial_menu | Statless | Main menu > Help > Exit |
| display_sarafu | Stateless | Main menu > My Sarafu > Exit |
| reset_pin | Stateful | Main menu > Account options > Reset pin > Exit |
### Stateful scenarios
These tests live individually in their own folder. They require some ENV variables to be set before running them.
### Stateless scenarios
These are included under the `stateless` folder and can be run in any order and have multple tests under the same folder since they only check correctness of menu responses.
## Running tests ## Running tests
```bash ```bash
$ npm run test # Set env variables for stateful scenarios, refer to individual test folders
$ npx tap --bail scenarios/SCENARIO_NAME
``` ```
## Scenarios Examples:
| Test | Description | - `CURRENT_PIN=4444 NEW_PIN=3333 npx tap --bail scenarios/stateless`
| ---------------- | ---------------------------- |
| 1_initial_menu | Main menu > Help > Exit | ### Limitations
| 2_display_sarafu | Main menu > My Sarafu > Exit |
- These scripts cannot reset USSD sessions upon failure. You will need to manually set your account USSD menu to the main menu
- Cannot verify sms status, confirm with cic-notify for delivery
### Writing your own scenario ### Writing your own scenario
@ -35,6 +55,8 @@ $ npm run test
- Control wait between menus with the `wait(time in ms)` function - Control wait between menus with the `wait(time in ms)` function
- Follow any spec - Follow any spec
- For any menu traversal, remember to append a `*` before the next input, this correctly mocks AT webhooks e.g. - For any menu traversal, remember to append a `*` before the next input, this correctly mocks AT webhooks e.g.
- Dyanmic inputs for stateful scenarios must come from an env var that can be set again on next run
- Include a `tap-parallel-not-ok` file in any new folder
```bash ```bash
# Initial Main menu, no input # Initial Main menu, no input

13
config.js Normal file
View File

@ -0,0 +1,13 @@
module.exports = {
ussd: {
serviceCode: "*483*061#",
endpoint: "https://ussd.grassecon.net",
timeout: 3000,
},
test: {
waitNextMenu: 3000,
},
user: {
ussdPhone: "254711777734",
},
};

14
lib.js
View File

@ -1,17 +1,19 @@
const crypto = require("crypto"); const crypto = require("crypto");
const phin = require("phin"); const phin = require("phin");
async function ussdClient(sessionId, input = "") { const conf = require("./config");
async function ussdClient(phone, sessionId, input = "") {
const requestOptions = { const requestOptions = {
url: "https://ussd.grassecon.net", url: conf.ussd.endpoint,
method: "POST", method: "POST",
parse: "string", parse: "string",
timeout: 3000, timeout: conf.ussd.timeout,
form: { form: {
sessionId: sessionId, sessionId: sessionId,
// Get from a config file, then pass as a param // Get from a conf file, then pass as a param
phoneNumber: "254711777734", phoneNumber: phone,
serviceCode: "*483*061#", serviceCode: conf.ussd.serviceCode,
text: input, text: input,
}, },
}; };

View File

@ -0,0 +1,113 @@
const test = require("tap").test;
const rangi = require("rangi");
const lib = require("../../lib");
const conf = require("../../config");
test("Reset Pin", async (t) => {
t.before((t) => {
if (!process.env.CURRENT_PIN || !process.env.NEW_PIN) {
t.bailout("ENV not set");
}
});
const sessionId = lib.newSession();
const currentPin = process.env.CURRENT_PIN;
const newPin = process.env.NEW_PIN;
t.test("Display menu and Sarafu balance", async (t) => {
await lib.wait(conf.test.waitNextMenu);
const r = await lib.ussdClient(conf.user.ussdPhone, sessionId);
console.log(rangi.cyan(r.text));
t.equal(r.code, "CON");
t.match(r.text, /Balance/g);
t.end();
});
t.test("Go to My Accont menu", async (t) => {
await lib.wait(conf.test.waitNextMenu);
const r = await lib.ussdClient(conf.user.ussdPhone, sessionId, "3");
console.log(rangi.cyan(r.text));
t.equal(r.code, "CON");
t.match(r.text, /PIN options/g);
t.end();
});
t.test("Go to Pin options", async (t) => {
await lib.wait(conf.test.waitNextMenu);
const r = await lib.ussdClient(conf.user.ussdPhone, sessionId, "3*5");
console.log(rangi.cyan(r.text));
t.equal(r.code, "CON");
t.match(r.text, /Change my PIN/g);
t.end();
});
t.test("Start Pin Change", async (t) => {
await lib.wait(conf.test.waitNextMenu);
const r = await lib.ussdClient(conf.user.ussdPhone, sessionId, "3*5*1");
console.log(rangi.cyan(r.text));
t.equal(r.code, "CON");
t.match(r.text, /Enter current PIN/g);
t.end();
});
t.test("Enter current Pin", async (t) => {
await lib.wait(conf.test.waitNextMenu);
const r = await lib.ussdClient(
conf.user.ussdPhone,
sessionId,
`3*5*1*${currentPin}`
);
console.log(rangi.cyan(r.text));
t.equal(r.code, "CON");
t.match(r.text, /new four number PIN/g);
t.end();
});
t.test("Enter new Pin", async (t) => {
await lib.wait(conf.test.waitNextMenu);
const r = await lib.ussdClient(
conf.user.ussdPhone,
sessionId,
`3*5*1*${currentPin}*${newPin}`
);
console.log(rangi.cyan(r.text));
t.equal(r.code, "CON");
t.match(r.text, /new four number PIN again/g);
t.end();
});
t.test("Enter new Pin agin", async (t) => {
await lib.wait(conf.test.waitNextMenu);
const r = await lib.ussdClient(
conf.user.ussdPhone,
sessionId,
`3*5*1*${currentPin}*${newPin}*${newPin}`
);
console.log(rangi.cyan(r.text));
t.equal(r.code, "CON");
t.match(r.text, /Your request has been sent/g);
t.end();
});
t.test("Exit", async (t) => {
await lib.wait(conf.test.waitNextMenu);
const r = await lib.ussdClient(
conf.user.ussdPhone,
sessionId,
`3*5*1*${currentPin}*${newPin}*${newPin}*99`
);
console.log(rangi.cyan(r.text));
t.equal(r.code, "END");
t.match(r.text, /Thank/g);
t.end();
});
});

View File

@ -1,16 +1,17 @@
const test = require("tap").test; const test = require("tap").test;
const rangi = require("rangi"); const rangi = require("rangi");
const lib = require("../lib"); const lib = require("../../lib");
const conf = require("../../config");
test("Initial menu, display balances, exit", async (t) => { test("Display Sarafu", async (t) => {
const sessionId = lib.newSession(); const sessionId = lib.newSession();
t.plan(3); t.plan(3);
t.test("Display menu and Sarafu balance", async (t) => { t.test("Display menu and Sarafu balance", async (t) => {
await lib.wait(3000); await lib.wait(conf.test.waitNextMenu);
const r = await lib.ussdClient(sessionId); const r = await lib.ussdClient(conf.user.ussdPhone, sessionId);
console.log(rangi.cyan(r.text)); console.log(rangi.cyan(r.text));
t.equal(r.code, "CON"); t.equal(r.code, "CON");
@ -19,8 +20,8 @@ test("Initial menu, display balances, exit", async (t) => {
}); });
t.test("Go to My Sarafu menu", async (t) => { t.test("Go to My Sarafu menu", async (t) => {
await lib.wait(3000); await lib.wait(conf.test.waitNextMenu);
const r = await lib.ussdClient(sessionId, "2"); const r = await lib.ussdClient(conf.user.ussdPhone, sessionId, "2");
console.log(rangi.cyan(r.text)); console.log(rangi.cyan(r.text));
t.equal(r.code, "CON"); t.equal(r.code, "CON");
@ -29,8 +30,8 @@ test("Initial menu, display balances, exit", async (t) => {
}); });
t.test("Exit", async (t) => { t.test("Exit", async (t) => {
await lib.wait(3000); await lib.wait(conf.test.waitNextMenu);
const r = await lib.ussdClient(sessionId, "2*00"); const r = await lib.ussdClient(conf.user.ussdPhone, sessionId, "2*00");
console.log(rangi.cyan(r.text)); console.log(rangi.cyan(r.text));
t.equal(r.code, "END"); t.equal(r.code, "END");

View File

@ -1,16 +1,17 @@
const test = require("tap").test; const test = require("tap").test;
const rangi = require("rangi"); const rangi = require("rangi");
const lib = require("../lib"); const lib = require("../../lib");
const conf = require("../../config");
test("Initial menu, go to help, exit", async (t) => { test("Initial Menu", async (t) => {
const sessionId = lib.newSession(); const sessionId = lib.newSession();
t.plan(3); t.plan(3);
t.test("Display menu and Sarafu balance", async (t) => { t.test("Display menu and Sarafu balance", async (t) => {
await lib.wait(3000); await lib.wait(conf.test.waitNextMenu);
const r = await lib.ussdClient(sessionId); const r = await lib.ussdClient(conf.user.ussdPhone, sessionId);
console.log(rangi.cyan(r.text)); console.log(rangi.cyan(r.text));
t.equal(r.code, "CON"); t.equal(r.code, "CON");
@ -19,8 +20,8 @@ test("Initial menu, go to help, exit", async (t) => {
}); });
t.test("Go to help menu", async (t) => { t.test("Go to help menu", async (t) => {
await lib.wait(3000); await lib.wait(conf.test.waitNextMenu);
const r = await lib.ussdClient(sessionId, "4"); const r = await lib.ussdClient(conf.user.ussdPhone, sessionId, "4");
console.log(rangi.cyan(r.text)); console.log(rangi.cyan(r.text));
t.equal(r.code, "CON"); t.equal(r.code, "CON");
@ -29,8 +30,8 @@ test("Initial menu, go to help, exit", async (t) => {
}); });
t.test("Exit", async (t) => { t.test("Exit", async (t) => {
await lib.wait(3000); await lib.wait(conf.test.waitNextMenu);
const r = await lib.ussdClient(sessionId, "4*99"); const r = await lib.ussdClient(conf.user.ussdPhone, sessionId, "4*99");
console.log(rangi.cyan(r.text)); console.log(rangi.cyan(r.text));
t.equal(r.code, "END"); t.equal(r.code, "END");

View File

View File

@ -448,7 +448,7 @@ browserslist@^4.17.5:
resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.20.2.tgz#567b41508757ecd904dab4d1c646c612cd3d4f88" resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.20.2.tgz#567b41508757ecd904dab4d1c646c612cd3d4f88"
integrity sha512-CQOBCqp/9pDvDbx3xfMi+86pr4KXIf2FDkTTdeuYw8OxS9t898LA1Khq57gtufFILXpfgsSx5woNgsBgvGjpsA== integrity sha512-CQOBCqp/9pDvDbx3xfMi+86pr4KXIf2FDkTTdeuYw8OxS9t898LA1Khq57gtufFILXpfgsSx5woNgsBgvGjpsA==
dependencies: dependencies:
caniuse-lite "^1.0.30001317" caniuse-lite "^1.0.config.test.waitNextMenu1317"
electron-to-chromium "^1.4.84" electron-to-chromium "^1.4.84"
escalade "^3.1.1" escalade "^3.1.1"
node-releases "^2.0.2" node-releases "^2.0.2"
@ -493,9 +493,9 @@ camelcase@^5.0.0, camelcase@^5.3.1:
resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-5.3.1.tgz#e3c9b31569e106811df242f715725a1f4c494320" resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-5.3.1.tgz#e3c9b31569e106811df242f715725a1f4c494320"
integrity sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg== integrity sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==
caniuse-lite@^1.0.30001317: caniuse-lite@^1.0.config.test.waitNextMenu1317:
version "1.0.30001320" version "1.0.config.test.waitNextMenu1320"
resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001320.tgz#8397391bec389b8ccce328636499b7284ee13285" resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.config.test.waitNextMenu1320.tgz#8397391bec389b8ccce328636499b7284ee13285"
integrity sha512-MWPzG54AGdo3nWx7zHZTefseM5Y1ccM7hlQKHRqJkPozUaw3hNbBTMmLn16GG2FUzjR13Cr3NPfhIieX5PzXDA== integrity sha512-MWPzG54AGdo3nWx7zHZTefseM5Y1ccM7hlQKHRqJkPozUaw3hNbBTMmLn16GG2FUzjR13Cr3NPfhIieX5PzXDA==
cardinal@^2.1.1: cardinal@^2.1.1: