Merge branch 'master' into lash/new-sarafu-token
This commit is contained in:
commit
03ac6633a2
3
.gitignore
vendored
3
.gitignore
vendored
@ -8,3 +8,6 @@ gmon.out
|
|||||||
*.egg-info
|
*.egg-info
|
||||||
dist/
|
dist/
|
||||||
build/
|
build/
|
||||||
|
**/*sqlite
|
||||||
|
**/.nyc_output
|
||||||
|
**/coverage
|
||||||
|
2
apps/cic-meta/.gitignore
vendored
2
apps/cic-meta/.gitignore
vendored
@ -3,3 +3,5 @@ dist
|
|||||||
dist-web
|
dist-web
|
||||||
dist-server
|
dist-server
|
||||||
scratch
|
scratch
|
||||||
|
coverage
|
||||||
|
.nyc_output
|
||||||
|
@ -3,17 +3,38 @@
|
|||||||
variables:
|
variables:
|
||||||
APP_NAME: cic-meta
|
APP_NAME: cic-meta
|
||||||
DOCKERFILE_PATH: $APP_NAME/docker/Dockerfile
|
DOCKERFILE_PATH: $APP_NAME/docker/Dockerfile
|
||||||
|
IMAGE_TAG: $CI_REGISTRY_IMAGE/$APP_NAME:unittest-$CI_COMMIT_SHORT_SHA
|
||||||
|
|
||||||
.cic_meta_changes_target:
|
.cic_meta_changes_target:
|
||||||
rules:
|
rules:
|
||||||
- changes:
|
- if: $CI_PIPELINE_SOURCE == "merge_request_event"
|
||||||
- $CONTEXT/$APP_NAME/*
|
# - changes:
|
||||||
|
# - $CONTEXT/$APP_NAME/*
|
||||||
|
- when: always
|
||||||
|
|
||||||
build-mr-cic-meta:
|
cic-meta-build-mr:
|
||||||
|
stage: build
|
||||||
extends:
|
extends:
|
||||||
- .cic_meta_changes_target
|
|
||||||
- .py_build_merge_request
|
|
||||||
- .cic_meta_variables
|
- .cic_meta_variables
|
||||||
|
- .cic_meta_changes_target
|
||||||
|
script:
|
||||||
|
- mkdir -p /kaniko/.docker
|
||||||
|
- echo "{\"auths\":{\"$CI_REGISTRY\":{\"username\":\"$CI_REGISTRY_USER\",\"password\":\"$CI_REGISTRY_PASSWORD\"}}}" > "/kaniko/.docker/config.json"
|
||||||
|
# - /kaniko/executor --context $CONTEXT --dockerfile $DOCKERFILE_PATH $KANIKO_CACHE_ARGS --destination $IMAGE_TAG
|
||||||
|
- /kaniko/executor --context $CONTEXT --dockerfile $DOCKERFILE_PATH $KANIKO_CACHE_ARGS --destination $IMAGE_TAG
|
||||||
|
|
||||||
|
test-mr-cic-meta:
|
||||||
|
extends:
|
||||||
|
- .cic_meta_variables
|
||||||
|
- .cic_meta_changes_target
|
||||||
|
stage: test
|
||||||
|
image: $IMAGE_TAG
|
||||||
|
script:
|
||||||
|
- cd /tmp/src/cic-meta
|
||||||
|
- npm install --dev
|
||||||
|
- npm run test
|
||||||
|
- npm run test:coverage
|
||||||
|
needs: ["cic-meta-build-mr"]
|
||||||
|
|
||||||
build-push-cic-meta:
|
build-push-cic-meta:
|
||||||
extends:
|
extends:
|
||||||
|
@ -4,29 +4,28 @@ WORKDIR /tmp/src/cic-meta
|
|||||||
|
|
||||||
RUN apk add --no-cache postgresql bash
|
RUN apk add --no-cache postgresql bash
|
||||||
|
|
||||||
COPY cic-meta/package.json \
|
# required to build the cic-client-meta module
|
||||||
./
|
|
||||||
|
|
||||||
COPY cic-meta/src/ src/
|
COPY cic-meta/src/ src/
|
||||||
COPY cic-meta/tests/ tests/
|
|
||||||
COPY cic-meta/scripts/ scripts/
|
COPY cic-meta/scripts/ scripts/
|
||||||
|
|
||||||
|
# copy the dependencies
|
||||||
|
COPY cic-meta/package.json .
|
||||||
|
COPY cic-meta/tsconfig.json .
|
||||||
|
COPY cic-meta/webpack.config.js .
|
||||||
|
|
||||||
RUN npm install
|
RUN npm install
|
||||||
|
|
||||||
# see exports_dir gpg.ini
|
COPY cic-meta/tests/ tests/
|
||||||
COPY cic-meta/tests/*.asc /root/pgp/
|
COPY cic-meta/tests/*.asc /root/pgp/
|
||||||
RUN alias tsc=node_modules/typescript/bin/tsc
|
|
||||||
|
|
||||||
|
|
||||||
|
# copy runtime configs
|
||||||
COPY cic-meta/.config/ /usr/local/etc/cic-meta/
|
COPY cic-meta/.config/ /usr/local/etc/cic-meta/
|
||||||
# COPY cic-meta/scripts/server/initdb/server.postgres.sql /usr/local/share/cic-meta/sql/server.sql
|
|
||||||
|
|
||||||
|
# db migrations
|
||||||
COPY cic-meta/docker/db.sh ./db.sh
|
COPY cic-meta/docker/db.sh ./db.sh
|
||||||
RUN chmod 755 ./db.sh
|
RUN chmod 755 ./db.sh
|
||||||
|
|
||||||
#RUN alias ts-node=/tmp/src/cic-meta/node_modules/ts-node/dist/bin.js
|
RUN alias tsc=node_modules/typescript/bin/tsc
|
||||||
#ENTRYPOINT [ "./node_modules/ts-node/dist/bin.js", "./scripts/server/server.ts" ]
|
|
||||||
|
|
||||||
COPY cic-meta/docker/start_server.sh ./start_server.sh
|
COPY cic-meta/docker/start_server.sh ./start_server.sh
|
||||||
RUN chmod 755 ./start_server.sh
|
RUN chmod 755 ./start_server.sh
|
||||||
ENTRYPOINT ["sh", "./start_server.sh"]
|
ENTRYPOINT ["sh", "./start_server.sh"]
|
||||||
|
2362
apps/cic-meta/package-lock.json
generated
2362
apps/cic-meta/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -11,6 +11,7 @@
|
|||||||
"preferGlobal": true,
|
"preferGlobal": true,
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"test": "mocha -r node_modules/node-localstorage/register -r ts-node/register tests/*.ts",
|
"test": "mocha -r node_modules/node-localstorage/register -r ts-node/register tests/*.ts",
|
||||||
|
"test:coverage": "nyc mocha tests/*.ts --timeout 3000 --check-coverage=true",
|
||||||
"build": "node_modules/typescript/bin/tsc -d --outDir dist src/index.ts",
|
"build": "node_modules/typescript/bin/tsc -d --outDir dist src/index.ts",
|
||||||
"build-server": "tsc -d --outDir dist-server scripts/server/*.ts",
|
"build-server": "tsc -d --outDir dist-server scripts/server/*.ts",
|
||||||
"pack": "node_modules/typescript/bin/tsc -d --outDir dist && webpack",
|
"pack": "node_modules/typescript/bin/tsc -d --outDir dist && webpack",
|
||||||
@ -34,7 +35,9 @@
|
|||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/mocha": "^8.0.3",
|
"@types/mocha": "^8.0.3",
|
||||||
"mocha": "^8.2.0",
|
"mocha": "^8.2.0",
|
||||||
|
"nock": "^13.1.0",
|
||||||
"node-localstorage": "^2.1.6",
|
"node-localstorage": "^2.1.6",
|
||||||
|
"nyc": "^15.1.0",
|
||||||
"ts-node": "^9.0.0",
|
"ts-node": "^9.0.0",
|
||||||
"typescript": "^4.0.5",
|
"typescript": "^4.0.5",
|
||||||
"webpack": "^5.4.0",
|
"webpack": "^5.4.0",
|
||||||
@ -50,5 +53,26 @@
|
|||||||
"license": "GPL-3.0-or-later",
|
"license": "GPL-3.0-or-later",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=14.16.1"
|
"node": ">=14.16.1"
|
||||||
|
},
|
||||||
|
"nyc": {
|
||||||
|
"include": [
|
||||||
|
"src/**/*.ts"
|
||||||
|
],
|
||||||
|
"extension": [
|
||||||
|
".ts"
|
||||||
|
],
|
||||||
|
"require": [
|
||||||
|
"ts-node/register"
|
||||||
|
],
|
||||||
|
"reporter": [
|
||||||
|
"text",
|
||||||
|
"html"
|
||||||
|
],
|
||||||
|
"sourceMap": true,
|
||||||
|
"instrument": true,
|
||||||
|
"branches": ">80",
|
||||||
|
"lines": ">80",
|
||||||
|
"functions": ">80",
|
||||||
|
"statements": ">80"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -204,7 +204,7 @@ async function processRequest(req, res) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (content === undefined) {
|
if (content === undefined) {
|
||||||
console.error('empty onctent', data);
|
console.error('empty content', data);
|
||||||
res.writeHead(400, {"Content-Type": "text/plain"});
|
res.writeHead(400, {"Content-Type": "text/plain"});
|
||||||
res.end();
|
res.end();
|
||||||
return;
|
return;
|
||||||
|
@ -9,7 +9,7 @@ class Custom extends Syncable implements Addressable {
|
|||||||
super('', v);
|
super('', v);
|
||||||
Custom.toKey(name).then((cid) => {
|
Custom.toKey(name).then((cid) => {
|
||||||
this.id = cid;
|
this.id = cid;
|
||||||
this.value = v;
|
this.name = name;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -100,13 +100,15 @@ class Meta {
|
|||||||
identifier = await User.toKey(name);
|
identifier = await User.toKey(name);
|
||||||
} else if (type === 'phone') {
|
} else if (type === 'phone') {
|
||||||
identifier = await Phone.toKey(name);
|
identifier = await Phone.toKey(name);
|
||||||
} else {
|
} else if (type === 'custom') {
|
||||||
identifier = await Custom.toKey(name);
|
identifier = await Custom.toKey(name);
|
||||||
|
} else {
|
||||||
|
identifier = await Custom.toKey(name, type);
|
||||||
}
|
}
|
||||||
return identifier;
|
return identifier;
|
||||||
}
|
}
|
||||||
|
|
||||||
private wrap(syncable: Syncable): Promise<Envelope> {
|
wrap(syncable: Syncable): Promise<Envelope> {
|
||||||
return new Promise<Envelope>(async (resolve, reject) => {
|
return new Promise<Envelope>(async (resolve, reject) => {
|
||||||
syncable.setSigner(this.signer);
|
syncable.setSigner(this.signer);
|
||||||
syncable.onwrap = async (env) => {
|
syncable.onwrap = async (env) => {
|
||||||
|
49
apps/cic-meta/tests/custom.ts
Normal file
49
apps/cic-meta/tests/custom.ts
Normal file
@ -0,0 +1,49 @@
|
|||||||
|
import * as assert from 'assert';
|
||||||
|
import {Custom} from "../src";
|
||||||
|
|
||||||
|
const testName = 'areas';
|
||||||
|
const testObject = {
|
||||||
|
area: ['Nairobi', 'Mombasa', 'Kilifi']
|
||||||
|
}
|
||||||
|
const testNameKey = '8f3da0c90ba2b89ff217da96f6088cbaf987a1b58bc33c3a5e526e53cec7cfed';
|
||||||
|
const testIdentifier = ':cic.area'
|
||||||
|
const testIdentifierKey = 'da6194e6f33726546e82c328df4c120b844d6427859156518bd600765bf8b2b7';
|
||||||
|
|
||||||
|
describe('custom', () => {
|
||||||
|
|
||||||
|
context('with predefined data', () => {
|
||||||
|
it('should create a custom object', () => {
|
||||||
|
const custom = new Custom(testName, testObject);
|
||||||
|
setTimeout(() => {
|
||||||
|
assert.strictEqual(custom.name, testName);
|
||||||
|
assert.deepStrictEqual(custom.m.data, testObject);
|
||||||
|
assert.strictEqual(custom.key(), testNameKey)
|
||||||
|
}, 0);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
context('without predefined data', () => {
|
||||||
|
it('should create a custom object', () => {
|
||||||
|
const custom = new Custom(testName);
|
||||||
|
setTimeout(() => {
|
||||||
|
assert.strictEqual(custom.name, testName);
|
||||||
|
assert.deepStrictEqual(custom.m.data, {});
|
||||||
|
assert.strictEqual(custom.key(), testNameKey)
|
||||||
|
}, 0);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('#toKey()', () => {
|
||||||
|
context('without a custom identifier', () => {
|
||||||
|
it('should generate a key from the custom name', async () => {
|
||||||
|
assert.strictEqual(await Custom.toKey(testName), testNameKey);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
context('with a custom identifier', () => {
|
||||||
|
it('should generate a key from the custom name with a custom identifier', async () => {
|
||||||
|
assert.strictEqual(await Custom.toKey(testName, testIdentifier), testIdentifierKey);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
176
apps/cic-meta/tests/meta.ts
Normal file
176
apps/cic-meta/tests/meta.ts
Normal file
@ -0,0 +1,176 @@
|
|||||||
|
import * as assert from 'assert';
|
||||||
|
import * as fs from 'fs';
|
||||||
|
const nock = require('nock');
|
||||||
|
import {Meta} from "../src";
|
||||||
|
import {getResponse, metaData, networkErrorResponse, notFoundResponse, putResponse} from "./response";
|
||||||
|
import {Syncable} from "@cicnet/crdt-meta";
|
||||||
|
|
||||||
|
const metaUrl = 'https://meta.dev.grassrootseconomics.net';
|
||||||
|
const testAddress = '0xc1912fee45d61c87cc5ea59dae31190fffff232d';
|
||||||
|
const testAddressKey = 'a51472cb4df63b199a4de01335b1b4d1bbee27ff4f03340aa1d592f26c6acfe2';
|
||||||
|
const testPhone = '+254123456789';
|
||||||
|
const testPhoneKey = 'be3cc8212b7eb57c6217ddd42230bd8ccd2f01382bf8c1c77d3a683fa5a9bb16';
|
||||||
|
const testName = 'areas'
|
||||||
|
const testNameKey = '8f3da0c90ba2b89ff217da96f6088cbaf987a1b58bc33c3a5e526e53cec7cfed';
|
||||||
|
const testIdentifier = ':cic.area'
|
||||||
|
const testIdentifierKey = 'da6194e6f33726546e82c328df4c120b844d6427859156518bd600765bf8b2b7';
|
||||||
|
|
||||||
|
function readFile(filename) {
|
||||||
|
if(!fs.existsSync(filename)) {
|
||||||
|
console.error(`File ${filename} not found`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
return fs.readFileSync(filename, {encoding: 'utf8', flag: 'r'});
|
||||||
|
}
|
||||||
|
|
||||||
|
const privateKey = readFile('./privatekeys.asc');
|
||||||
|
|
||||||
|
describe('meta', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
nock(metaUrl)
|
||||||
|
.get(`/${testAddressKey}`)
|
||||||
|
.reply(200, getResponse);
|
||||||
|
|
||||||
|
nock(metaUrl)
|
||||||
|
.get(`/${testPhoneKey}`)
|
||||||
|
.reply(200, getResponse);
|
||||||
|
|
||||||
|
nock(metaUrl)
|
||||||
|
.get(`/${testAddress}`)
|
||||||
|
.reply(404);
|
||||||
|
|
||||||
|
nock(metaUrl)
|
||||||
|
.get(`/${testIdentifier}`)
|
||||||
|
.replyWithError(networkErrorResponse);
|
||||||
|
|
||||||
|
nock(metaUrl)
|
||||||
|
.put(`/${testAddressKey}`)
|
||||||
|
.reply(200, putResponse);
|
||||||
|
|
||||||
|
nock(metaUrl)
|
||||||
|
.put(`/${testAddress}`)
|
||||||
|
.reply(404);
|
||||||
|
|
||||||
|
nock(metaUrl)
|
||||||
|
.post('/post')
|
||||||
|
.reply(500);
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('#get()', () => {
|
||||||
|
it('should fetch data from the meta service', async () => {
|
||||||
|
const account = await Meta.get(testAddressKey, metaUrl);
|
||||||
|
assert.strictEqual(account.toJSON(account), getResponse.payload);
|
||||||
|
});
|
||||||
|
|
||||||
|
context('if item is not found', () => {
|
||||||
|
it('should respond with an error', async () => {
|
||||||
|
const account = await Meta.get(testAddress, metaUrl);
|
||||||
|
assert.strictEqual(account, `404: Not Found`);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
context('in case of network error', () => {
|
||||||
|
it('should respond with an error', async () => {
|
||||||
|
const account = await Meta.get(testIdentifier, metaUrl);
|
||||||
|
assert.strictEqual(account, `Request to ${metaUrl}/${testIdentifier} failed. Connection error.`);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('#set()', () => {
|
||||||
|
context('object data', () => {
|
||||||
|
it('should set data to the meta server', () => {
|
||||||
|
const meta = new Meta(metaUrl, privateKey);
|
||||||
|
meta.onload = async (status) => {
|
||||||
|
const response = await meta.set(testAddressKey, metaData);
|
||||||
|
assert.strictEqual(response, `${putResponse.status}: ${putResponse.statusText}`);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
context('string data', () => {
|
||||||
|
it('should set data to the meta server', () => {
|
||||||
|
const meta = new Meta(metaUrl, privateKey);
|
||||||
|
meta.onload = async (status) => {
|
||||||
|
const response = await meta.set(testPhoneKey, testAddress);
|
||||||
|
assert.strictEqual(response, `${putResponse.status}: ${putResponse.statusText}`);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
context('in case of network error', () => {
|
||||||
|
it('should respond with an error', () => {
|
||||||
|
const meta = new Meta(metaUrl, privateKey);
|
||||||
|
meta.onload = async (status) => {
|
||||||
|
const response = await meta.set(testIdentifier, metaData);
|
||||||
|
assert.strictEqual(response, `Request to ${metaUrl}/${testIdentifier} failed. Connection error.`);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('#updateMeta()', () => {
|
||||||
|
it('should update data in the meta server', async () => {
|
||||||
|
const syncable = new Syncable(testAddressKey, metaData);
|
||||||
|
const meta = new Meta(metaUrl, privateKey);
|
||||||
|
meta.onload = async (status) => {
|
||||||
|
const response = await meta.updateMeta(syncable, testAddressKey);
|
||||||
|
assert.strictEqual(response, putResponse);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
context('if item is not found', () => {
|
||||||
|
it('should respond with an error', () => {
|
||||||
|
const syncable = new Syncable(testAddress, metaData);
|
||||||
|
const meta = new Meta(metaUrl, privateKey);
|
||||||
|
meta.onload = async (status) => {
|
||||||
|
const response = await meta.updateMeta(syncable, testAddress);
|
||||||
|
assert.strictEqual(response, notFoundResponse);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('#wrap()', () => {
|
||||||
|
it('should sign a syncable object', function () {
|
||||||
|
const syncable = new Syncable(testAddressKey, metaData);
|
||||||
|
const meta = new Meta(metaUrl, privateKey);
|
||||||
|
meta.onload = async (status) => {
|
||||||
|
const response = await meta.wrap(syncable);
|
||||||
|
assert.strictEqual(response.toJSON(), getResponse);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('#getIdentifier()', () => {
|
||||||
|
context('without type', () => {
|
||||||
|
it('should return an identifier', async () => {
|
||||||
|
assert.strictEqual(await Meta.getIdentifier(testName), testNameKey);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
context('with user type', () => {
|
||||||
|
it('should return an identifier', async () => {
|
||||||
|
assert.strictEqual(await Meta.getIdentifier(testAddress, 'user'), testAddressKey);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
context('with phone type', () => {
|
||||||
|
it('should return an identifier', async () => {
|
||||||
|
assert.strictEqual(await Meta.getIdentifier(testPhone, 'phone'), testPhoneKey);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
context('with custom type', () => {
|
||||||
|
it('should return an identifier', async () => {
|
||||||
|
assert.strictEqual(await Meta.getIdentifier(testName, 'custom'), testNameKey);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
context('with unrecognised type', () => {
|
||||||
|
it('should return an identifier', async () => {
|
||||||
|
assert.strictEqual(await Meta.getIdentifier(testName, testIdentifier), testIdentifierKey);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
24
apps/cic-meta/tests/phone.ts
Normal file
24
apps/cic-meta/tests/phone.ts
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
import * as assert from 'assert';
|
||||||
|
import {Phone} from "../src";
|
||||||
|
|
||||||
|
const testAddress = '0xc1912fee45d61c87cc5ea59dae31190fffff232d';
|
||||||
|
const testPhone = '+254123456789';
|
||||||
|
const testPhoneKey = 'be3cc8212b7eb57c6217ddd42230bd8ccd2f01382bf8c1c77d3a683fa5a9bb16';
|
||||||
|
|
||||||
|
describe('phone', () => {
|
||||||
|
|
||||||
|
it('should create a phone object', () => {
|
||||||
|
const phone = new Phone(testAddress, testPhone);
|
||||||
|
setTimeout(() => {
|
||||||
|
assert.strictEqual(phone.address, testAddress);
|
||||||
|
assert.strictEqual(phone.m.data.msisdn, testPhone);
|
||||||
|
assert.strictEqual(phone.key(), testPhoneKey)
|
||||||
|
}, 0);
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('#toKey()', () => {
|
||||||
|
it('should generate a key from the phone number', async () => {
|
||||||
|
assert.strictEqual(await Phone.toKey(testPhone), testPhoneKey);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
79
apps/cic-meta/tests/response.ts
Normal file
79
apps/cic-meta/tests/response.ts
Normal file
File diff suppressed because one or more lines are too long
54
apps/cic-meta/tests/user.ts
Normal file
54
apps/cic-meta/tests/user.ts
Normal file
@ -0,0 +1,54 @@
|
|||||||
|
import * as assert from 'assert';
|
||||||
|
|
||||||
|
import { User } from "../src";
|
||||||
|
|
||||||
|
const testAddress = '0xc1912fee45d61c87cc5ea59dae31190fffff232d';
|
||||||
|
const testAddressKey = 'a51472cb4df63b199a4de01335b1b4d1bbee27ff4f03340aa1d592f26c6acfe2';
|
||||||
|
const testUser = {
|
||||||
|
user: {
|
||||||
|
firstName: 'Test',
|
||||||
|
lastName: 'User'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
describe('user', () => {
|
||||||
|
|
||||||
|
context('without predefined data', () => {
|
||||||
|
it('should create a user object', () => {
|
||||||
|
const user = new User(testAddress);
|
||||||
|
setTimeout(() => {
|
||||||
|
assert.strictEqual(user.address, testAddress);
|
||||||
|
assert.strictEqual(user.key(), testAddressKey);
|
||||||
|
assert.strictEqual(user.m.data.user.firstName, '');
|
||||||
|
assert.strictEqual(user.m.data.user.lastName, '');
|
||||||
|
}, 0);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
context('with predefined data', () => {
|
||||||
|
it('should create a user object', () => {
|
||||||
|
const user = new User(testAddress, testUser);
|
||||||
|
setTimeout(() => {
|
||||||
|
assert.strictEqual(user.address, testAddress);
|
||||||
|
assert.strictEqual(user.key(), testAddressKey);
|
||||||
|
assert.strictEqual(user.m.data.user.firstName, testUser.user.firstName);
|
||||||
|
assert.strictEqual(user.m.data.user.lastName, testUser.user.lastName);
|
||||||
|
}, 0);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('#setName()', () => {
|
||||||
|
it('should set user\'s names to metadata', () => {
|
||||||
|
const user = new User(testAddress);
|
||||||
|
user.setName(testUser.user.firstName, testUser.user.lastName);
|
||||||
|
assert.strictEqual(user.m.data.user.firstName, testUser.user.firstName);
|
||||||
|
assert.strictEqual(user.m.data.user.lastName, testUser.user.lastName);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('#toKey()', () => {
|
||||||
|
it('should generate a key from the user\'s address', async () => {
|
||||||
|
assert.strictEqual(await User.toKey(testAddress), testAddressKey);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
@ -2,7 +2,7 @@
|
|||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
"baseUrl": ".",
|
"baseUrl": ".",
|
||||||
"outDir": "./dist.browser",
|
"outDir": "./dist.browser",
|
||||||
"target": "es5",
|
"target": "es2015",
|
||||||
"module": "commonjs",
|
"module": "commonjs",
|
||||||
"moduleResolution": "node",
|
"moduleResolution": "node",
|
||||||
"lib": ["es2016", "dom", "es5"],
|
"lib": ["es2016", "dom", "es5"],
|
||||||
|
@ -5,6 +5,7 @@ LOCALE_PATH=/usr/src/cic-ussd/var/lib/locale/
|
|||||||
MAX_BODY_LENGTH=1024
|
MAX_BODY_LENGTH=1024
|
||||||
PASSWORD_PEPPER=QYbzKff6NhiQzY3ygl2BkiKOpER8RE/Upqs/5aZWW+I=
|
PASSWORD_PEPPER=QYbzKff6NhiQzY3ygl2BkiKOpER8RE/Upqs/5aZWW+I=
|
||||||
SERVICE_CODE=*483*46#,*483*061#,*384*96#
|
SERVICE_CODE=*483*46#,*483*061#,*384*96#
|
||||||
|
SUPPORT_PHONE_NUMBER=0757628885
|
||||||
|
|
||||||
[phone_number]
|
[phone_number]
|
||||||
REGION=KE
|
REGION=KE
|
||||||
|
@ -5,6 +5,7 @@ LOCALE_PATH=var/lib/locale/
|
|||||||
MAX_BODY_LENGTH=1024
|
MAX_BODY_LENGTH=1024
|
||||||
PASSWORD_PEPPER=QYbzKff6NhiQzY3ygl2BkiKOpER8RE/Upqs/5aZWW+I=
|
PASSWORD_PEPPER=QYbzKff6NhiQzY3ygl2BkiKOpER8RE/Upqs/5aZWW+I=
|
||||||
SERVICE_CODE=*483*46#
|
SERVICE_CODE=*483*46#
|
||||||
|
SUPPORT_PHONE_NUMBER=0757628885
|
||||||
|
|
||||||
[ussd]
|
[ussd]
|
||||||
MENU_FILE=/usr/local/lib/python3.8/site-packages/cic_ussd/db/ussd_menu.json
|
MENU_FILE=/usr/local/lib/python3.8/site-packages/cic_ussd/db/ussd_menu.json
|
||||||
|
@ -41,3 +41,7 @@ def get_user_by_phone_number(phone_number: str) -> Optional[Account]:
|
|||||||
phone_number = process_phone_number(phone_number=phone_number, region='KE')
|
phone_number = process_phone_number(phone_number=phone_number, region='KE')
|
||||||
user = Account.session.query(Account).filter_by(phone_number=phone_number).first()
|
user = Account.session.query(Account).filter_by(phone_number=phone_number).first()
|
||||||
return user
|
return user
|
||||||
|
|
||||||
|
|
||||||
|
class Support:
|
||||||
|
phone_number = None
|
||||||
|
@ -19,7 +19,7 @@ from cic_ussd.db.models.ussd_session import UssdSession
|
|||||||
from cic_ussd.error import MetadataNotFoundError, SeppukuError
|
from cic_ussd.error import MetadataNotFoundError, SeppukuError
|
||||||
from cic_ussd.menu.ussd_menu import UssdMenu
|
from cic_ussd.menu.ussd_menu import UssdMenu
|
||||||
from cic_ussd.metadata import blockchain_address_to_metadata_pointer
|
from cic_ussd.metadata import blockchain_address_to_metadata_pointer
|
||||||
from cic_ussd.phone_number import get_user_by_phone_number
|
from cic_ussd.phone_number import get_user_by_phone_number, Support
|
||||||
from cic_ussd.redis import cache_data, create_cached_data_key, get_cached_data
|
from cic_ussd.redis import cache_data, create_cached_data_key, get_cached_data
|
||||||
from cic_ussd.state_machine import UssdStateMachine
|
from cic_ussd.state_machine import UssdStateMachine
|
||||||
from cic_ussd.conversions import to_wei, from_wei
|
from cic_ussd.conversions import to_wei, from_wei
|
||||||
@ -270,7 +270,24 @@ def process_display_user_metadata(user: Account, display_key: str):
|
|||||||
products=products
|
products=products
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
raise MetadataNotFoundError(f'Expected person metadata but found none in cache for key: {key}')
|
# TODO [Philip]: All these translations could be moved to translation files.
|
||||||
|
logg.warning(f'Expected person metadata but found none in cache for key: {key}')
|
||||||
|
|
||||||
|
absent = ''
|
||||||
|
if user.preferred_language == 'en':
|
||||||
|
absent = 'Not provided'
|
||||||
|
elif user.preferred_language == 'sw':
|
||||||
|
absent = 'Haijawekwa'
|
||||||
|
|
||||||
|
return translation_for(
|
||||||
|
key=display_key,
|
||||||
|
preferred_language=user.preferred_language,
|
||||||
|
full_name=absent,
|
||||||
|
gender=absent,
|
||||||
|
location=absent,
|
||||||
|
products=absent
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def process_account_statement(user: Account, display_key: str, ussd_session: dict):
|
def process_account_statement(user: Account, display_key: str, ussd_session: dict):
|
||||||
@ -467,6 +484,14 @@ def next_state(ussd_session: dict, user: Account, user_input: str) -> str:
|
|||||||
return new_state
|
return new_state
|
||||||
|
|
||||||
|
|
||||||
|
def process_exit_invalid_menu_option(display_key: str, preferred_language: str):
|
||||||
|
return translation_for(
|
||||||
|
key=display_key,
|
||||||
|
preferred_language=preferred_language,
|
||||||
|
support_phone=Support.phone_number
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def custom_display_text(
|
def custom_display_text(
|
||||||
display_key: str,
|
display_key: str,
|
||||||
menu_name: str,
|
menu_name: str,
|
||||||
@ -503,5 +528,7 @@ def custom_display_text(
|
|||||||
return process_account_statement(display_key=display_key, user=user, ussd_session=ussd_session)
|
return process_account_statement(display_key=display_key, user=user, ussd_session=ussd_session)
|
||||||
elif menu_name == 'display_user_metadata':
|
elif menu_name == 'display_user_metadata':
|
||||||
return process_display_user_metadata(display_key=display_key, user=user)
|
return process_display_user_metadata(display_key=display_key, user=user)
|
||||||
|
elif menu_name == 'exit_invalid_menu_option':
|
||||||
|
return process_exit_invalid_menu_option(display_key=display_key, preferred_language=user.preferred_language)
|
||||||
else:
|
else:
|
||||||
return translation_for(key=display_key, preferred_language=user.preferred_language)
|
return translation_for(key=display_key, preferred_language=user.preferred_language)
|
||||||
|
@ -26,7 +26,7 @@ from cic_ussd.metadata.base import Metadata
|
|||||||
from cic_ussd.operations import (define_response_with_content,
|
from cic_ussd.operations import (define_response_with_content,
|
||||||
process_menu_interaction_requests,
|
process_menu_interaction_requests,
|
||||||
define_multilingual_responses)
|
define_multilingual_responses)
|
||||||
from cic_ussd.phone_number import process_phone_number
|
from cic_ussd.phone_number import process_phone_number, Support
|
||||||
from cic_ussd.processor import get_default_token_data
|
from cic_ussd.processor import get_default_token_data
|
||||||
from cic_ussd.redis import cache_data, create_cached_data_key, InMemoryStore
|
from cic_ussd.redis import cache_data, create_cached_data_key, InMemoryStore
|
||||||
from cic_ussd.requests import (get_request_endpoint,
|
from cic_ussd.requests import (get_request_endpoint,
|
||||||
@ -126,6 +126,8 @@ else:
|
|||||||
|
|
||||||
valid_service_codes = config.get('APP_SERVICE_CODE').split(",")
|
valid_service_codes = config.get('APP_SERVICE_CODE').split(",")
|
||||||
|
|
||||||
|
Support.phone_number = config.get('APP_SUPPORT_PHONE_NUMBER')
|
||||||
|
|
||||||
|
|
||||||
def application(env, start_response):
|
def application(env, start_response):
|
||||||
"""Loads python code for application to be accessible over web server
|
"""Loads python code for application to be accessible over web server
|
||||||
|
@ -11,7 +11,7 @@ from cic_ussd.balance import BalanceManager, compute_operational_balance
|
|||||||
from cic_ussd.chain import Chain
|
from cic_ussd.chain import Chain
|
||||||
from cic_ussd.db.models.account import AccountStatus, Account
|
from cic_ussd.db.models.account import AccountStatus, Account
|
||||||
from cic_ussd.operations import save_to_in_memory_ussd_session_data
|
from cic_ussd.operations import save_to_in_memory_ussd_session_data
|
||||||
from cic_ussd.phone_number import get_user_by_phone_number
|
from cic_ussd.phone_number import get_user_by_phone_number, process_phone_number
|
||||||
from cic_ussd.processor import retrieve_token_symbol
|
from cic_ussd.processor import retrieve_token_symbol
|
||||||
from cic_ussd.redis import create_cached_data_key, get_cached_data
|
from cic_ussd.redis import create_cached_data_key, get_cached_data
|
||||||
from cic_ussd.transactions import OutgoingTransactionProcessor
|
from cic_ussd.transactions import OutgoingTransactionProcessor
|
||||||
@ -30,7 +30,7 @@ def is_valid_recipient(state_machine_data: Tuple[str, dict, Account]) -> bool:
|
|||||||
"""
|
"""
|
||||||
user_input, ussd_session, user = state_machine_data
|
user_input, ussd_session, user = state_machine_data
|
||||||
recipient = get_user_by_phone_number(phone_number=user_input)
|
recipient = get_user_by_phone_number(phone_number=user_input)
|
||||||
is_not_initiator = user_input != user.phone_number
|
is_not_initiator = process_phone_number(user_input, 'KE') != user.phone_number
|
||||||
has_active_account_status = user.get_account_status() == AccountStatus.ACTIVE.name
|
has_active_account_status = user.get_account_status() == AccountStatus.ACTIVE.name
|
||||||
return is_not_initiator and has_active_account_status and recipient is not None
|
return is_not_initiator and has_active_account_status and recipient is not None
|
||||||
|
|
||||||
|
@ -36,26 +36,12 @@
|
|||||||
"source": "initial_pin_entry",
|
"source": "initial_pin_entry",
|
||||||
"dest": "exit_invalid_pin"
|
"dest": "exit_invalid_pin"
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"trigger": "scan_data",
|
|
||||||
"source": "initial_pin_confirmation",
|
|
||||||
"dest": "start",
|
|
||||||
"conditions": [
|
|
||||||
"cic_ussd.state_machine.logic.pin.pins_match",
|
|
||||||
"cic_ussd.state_machine.logic.validator.has_cached_user_metadata"
|
|
||||||
],
|
|
||||||
"after": [
|
|
||||||
"cic_ussd.state_machine.logic.pin.complete_pin_change",
|
|
||||||
"cic_ussd.state_machine.logic.user.get_user_metadata",
|
|
||||||
"cic_ussd.state_machine.logic.user.update_account_status_to_active"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"trigger": "scan_data",
|
"trigger": "scan_data",
|
||||||
"source": "initial_pin_confirmation",
|
"source": "initial_pin_confirmation",
|
||||||
"unless": "cic_ussd.state_machine.logic.validator.has_cached_user_metadata",
|
"unless": "cic_ussd.state_machine.logic.validator.has_cached_user_metadata",
|
||||||
"conditions": "cic_ussd.state_machine.logic.pin.pins_match",
|
"conditions": "cic_ussd.state_machine.logic.pin.pins_match",
|
||||||
"dest": "enter_given_name",
|
"dest": "start",
|
||||||
"after": [
|
"after": [
|
||||||
"cic_ussd.state_machine.logic.pin.complete_pin_change",
|
"cic_ussd.state_machine.logic.pin.complete_pin_change",
|
||||||
"cic_ussd.state_machine.logic.user.update_account_status_to_active"
|
"cic_ussd.state_machine.logic.user.update_account_status_to_active"
|
||||||
|
Loading…
Reference in New Issue
Block a user