Create a crypto payment forwarder - node.js

I am trying to make my own cryptocurrency payment forwarder, (like https://apirone.com/), but I am facing an issue, this is my code:
const CryptoAccount = require('send-crypto');
const WebSocket = require('ws');
const qrcode = require('qrcode-terminal');
const clipboardy = require('clipboardy');
const init = async function() {
const socket = new WebSocket('wss://ws.blockchain.info/inv');
const wallets = [];
socket.on('open', stream => {
console.log('WebSocket opened');
setInterval(() => socket.send(JSON.stringify({"op": "ping"})), 10000);
});
socket.on('error', err => {
console.error(err.message);
});
socket.on('message', stream => {
try {
const response = JSON.parse(stream.toString('utf-8'));
if (JSON.stringify(response).includes('pong')) {
console.log('pong');
return;
}
console.log('SOCKET RESPONSE', response);
let outAddr = response.x.out[0].addr;
wallets.find(w => w.addr == outAddr).callback(response);
} catch (error) {
console.error(error);
}
})
const generateWallet = function() {
const privateKey = CryptoAccount.newPrivateKey();
const account = new CryptoAccount(privateKey);
return account;
}
const genWalletAndWatch = async function(currency, callback) {
if (currency !== 'BTC') throw new Error('Only BTC is supported currently.');
const wallet = generateWallet();
wallets.push({addr: (await wallet.address('BTC')), callback});
socket.send(JSON.stringify({"op":"addr_sub", "addr": (await wallet.address(currency))}));
return (await wallet.address(currency));
}
socket.onopen = async function(e) {
const wallet = await genWalletAndWatch('BTC', (response) => {
console.log('ANSWER', JSON.stringify(response));
});
console.log(wallet);
qrcode.generate('bitcoin:'+wallet, {small: true});
clipboardy.writeSync(wallet);
}
};
init();
This is a test code so there is no any express server or forward for the moment, I am only trying to create a btc address then to detect when a payment is done, but my problem is: The payment is never detected by the WebSocket, i can create a wallet without problem, I can ping the blockchain.info WebSocket api successfully, but the "addr_sub" never works, I've tried to send BTC etc, nothing is working.

Related

stripe CLI webhook not reciving in firebase without firebasefuctions

It is showing no error but it is not reaching Firebase.i am using stripe CLI
console.log(success: order ${session.id} had been added to db); this line never comes in the console.
the terminal shows everything created but it does not reach Firebase database I am thinking there is an error in the code. i think the firebase is not connecting
The stripe dashboard also says connected
I am using the forward to localhost line in git terminal
webhook code
import { buffer } from "micro";
import * as admin from 'firebase-admin'
//secure a connection to Firebase from backend
const serviceAccount = require('../../../permissions.json');
const app = !admin.apps.length ? admin.initializeApp({
credential: admin.credential.cert(serviceAccount),
})
: admin.app();
// establish connection to stripe
const stripe = require('stripe')(process.env.STRIPE_SECRET_KEY);
const endpointSecret = process.env.STRIPE_SIGNING_SECRET;
if (typeof endpointSecret !== "string") {
console.error("Unexpected value for STRIPE_SIGNING_SECRET");
// potentially throw an error here
}
const fulfillOrder = async (session) => {
//console.log('Fulfilling order', session)
return app
.firestore()
.collection("user")
.doc(session.metadata.email)
.collection("orders")
.doc(session.id)
.set({
amount: session.amount_total / 100,
amount_shipping: session.amount_total_details.amount_shipping / 100,
images: JSON.parse(session.metadata.images),
timestamp: admin.firestore.FieldValue.serverTimestamp(),
})
.then(() => {
console.log(`success: order ${session.id} had been added to db`);
});
};
export default async (req, res) =>{
if(req.method === 'post'){
const requestBuffer = await buffer(req);
const payload = requestBuffer.toString();
const sig = req.headers["stripe-signature"];
let event;
// verify that the event posted came from stripe
try{
event = stripe.webhooks.constructEvent(
payload,
sig,
endpointSecret);
} catch (err) {
console.log('ERROR', err.message)
return res.status(400).send(`Webhook error: ${err.message}`)
}
//handle the checkout event
if (event.type === 'checkout.session.completed') {
const session = event .data.object;
//fulfill the order...
return fulfillOrder(session)
.then(() => res.status(200))
.catch((err) => res.status(400).send(`Webhook error: ${err.message}`));
}
}
};
export const config = {
api: {
bodyParser: false,
externalResolver: true,
},
};
firebase rules
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
match /{document=**} {
allow write: if false;
allow read: if true;
}
}
}

NodeJS WebRTC app using DataChannel isn't working in production server

I created a simple peer-to-peer app using NodeJS and WebRTC for something like a one-to-many livestreaming application.
So far it is working on my localhost but when I deployed the app on a production VM server on Google Cloud Platform, I can't create a DataChannel using peer.createDataChannel(). Or at least that is the issue that I see because it is not throwing any errors.
server.js
const port = process.env.PORT || 80;
const express = require('express');
const bodyParser = require('body-parser');
const webrtc = require('wrtc');
const app = express();
const status = {
offline: 'offline',
online: 'online',
streaming: 'streaming'
};
let hostStream;
let hostChannel;
let channelData = {
status: status.offline,
message: null
};
app.use(express.static('public'));
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: true }));
app.post('/broadcast', async ({ body }, res) => {
try {
let peer = new webrtc.RTCPeerConnection({
iceServers: [
{
urls: "stun:stun.stunprotocol.org"
}
]
});
peer.ontrack = (e) => handleTrackEvent(e, peer);
peer.ondatachannel = (e) => handleHostDataChannelEvent(e);
let desc = new webrtc.RTCSessionDescription(body.sdp);
await peer.setRemoteDescription(desc);
let answer = await peer.createAnswer();
await peer.setLocalDescription(answer);
let payload = {
sdp: peer.localDescription,
status: channelData.status
};
res.json(payload);
} catch (e) {
console.log(e);
}
});
function handleTrackEvent(e, peer) {
hostStream = e.streams[0];
}
function handleHostDataChannelEvent(e) {
let channel = e.channel;
channel.onopen = function(event) {
channelData.message = '[ SERVER ]: Peer-to-peer data channel has been created.';
channel.send(JSON.stringify(channelData));
channelData.message = null;
}
channel.onmessage = function(event) {
console.log(event.data);
}
hostChannel = channel;
}
app.listen(port, () => console.log('[ SERVER ]: Started'));
streamer.js
function createPeer() {
let peer = new RTCPeerConnection({
iceServers: [
{
urls: "stun:stun.stunprotocol.org"
}
]
});
let channel = peer.createDataChannel('host-server');
channel.onopen = function(event) {
channel.send('Host: Data Channel Opened');
}
channel.onmessage = function(event) {
let data = JSON.parse(event.data);
if('status' in data) {
$('body').removeClass().addClass(data.status);
}
if('message' in data && data.message != null) {
$.toast({
heading: 'Data Channel',
text: data.message,
showHideTransition: 'slide',
icon: 'info',
position: 'top-center',
stack: false
})
}
}
peer.onnegotiationneeded = () => handleNegotiationNeededEvent(peer);
return peer;
}
On my localhost, when the host (streamer.js) starts streaming media, the server outputs Host: Data Channel Opened on the console and on the host's browser, I see the toast with a message Server: Peer-to-peer data channel has been created.. However when I try the application on my production server the server doesn't log that on the console and the host's browser doesn't open a toast with the message saying the data channel has been created.
There are no errors on both the browser console nor the server console so I don't really know where the problem is.
I do not see the gathering of ice candidates in your code - so it is no surprise your peers cannot establish a connection with each other. Here is the working sample of what your code should look like.
streamer.js:
async function createPeer(configuration) {
const localCandidates = [];
// Step 1. Create new RTCPeerConnection
const peer = new RTCPeerConnection(configuration);
peer.onconnectionstatechange = (event) => {
console.log('Connection state:', peer.connectionState);
};
peer.onsignalingstatechange = (event) => {
console.log('Signaling state:', peer.signalingState);
};
peer.oniceconnectionstatechange = (event) => {
console.log('ICE connection state:', peer.iceConnectionState);
};
peer.onicegatheringstatechange = (event) => {
console.log('ICE gathering state:', peer.iceGatheringState);
};
// Step 5. Gathering local ICE candidates
peer.onicecandidate = async (event) => {
if (event.candidate) {
localCandidates.push(event.candidate);
return;
}
// Step 6. Send Offer and client candidates to server
const response = await fetch('/broadcast', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
offer: offer,
candidates: localCandidates,
}),
});
const {answer, candidates} = await response.json();
// Step 7. Set remote description with Answer from server
await peer.setRemoteDescription(answer);
// Step 8. Add ICE candidates from server
for (let candidate of candidates) {
await peer.addIceCandidate(candidate);
}
};
// Step 2. Create new Data channel
const dataChannel = peer.createDataChannel('host-server');
dataChannel.onopen = (event) => {
dataChannel.send('Hello from client!');
};
dataChannel.onclose = (event) => {
console.log('Data channel closed');
};
dataChannel.onmessage = (event) => {
console.log('Data channel message:', event.data);
};
// Step 3. Create Offer
const offer = await peer.createOffer();
// Step 4. Set local description with Offer from step 3
await peer.setLocalDescription(offer);
return peer;
}
const configuration = {
iceServers: [
{
urls: 'stun:global.stun.twilio.com:3478?transport=udp',
},
],
};
// Add turn server to `configuration.iceServers` if needed.
// See more at https://www.twilio.com/docs/stun-turn
createPeer(configuration);
server.js:
const express = require('express');
const bodyParser = require('body-parser');
const webrtc = require('wrtc');
const port = process.env.PORT || 80;
const configuration = {
iceServers: [
{
urls: 'stun:global.stun.twilio.com:3478?transport=udp',
},
],
};
// Add turn server to `configuration.iceServers` if needed.
const app = express();
app.use(express.static('public'));
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({extended: true}));
app.post('/broadcast', async (req, res) => {
const {offer, candidates} = req.body;
const localCandidates = [];
let dataChannel;
// Step 1. Create new RTCPeerConnection
const peer = new webrtc.RTCPeerConnection(configuration);
peer.ondatachannel = (event) => {
dataChannel = event.channel;
dataChannel.onopen = (event) => {
dataChannel.send('Hello from server!');
};
dataChannel.onclose = (event) => {
console.log('Data channel closed');
};
dataChannel.onmessage = (event) => {
console.log('Data channel message:', event.data);
};
};
peer.onconnectionstatechange = (event) => {
console.log('Connection state:', peer.connectionState);
};
peer.onsignalingstatechange = (event) => {
console.log('Signaling state:', peer.signalingState);
};
peer.oniceconnectionstatechange = (event) => {
console.log('ICE connection state:', peer.iceConnectionState);
};
peer.onicegatheringstatechange = (event) => {
console.log('ICE gathering state:', peer.iceGatheringState);
};
peer.onicecandidate = (event) => {
// Step 6. Gathering local ICE candidates
if (event.candidate) {
localCandidates.push(event.candidate);
return;
}
// Step 7. Response with Answer and server candidates
let payload = {
answer: peer.localDescription,
candidates: localCandidates,
};
res.json(payload);
};
// Step 2. Set remote description with Offer from client
await peer.setRemoteDescription(offer);
// Step 3. Create Answer
let answer = await peer.createAnswer();
// Step 4. Set local description with Answer from step 3
await peer.setLocalDescription(answer);
// Step 5. Add ICE candidates from client
for (let candidate of candidates) {
await peer.addIceCandidate(candidate);
}
});
app.listen(port, () => console.log('Server started on port ' + port));
I found your stun server not fully functional, so I replaced it with another one from Twillio. Also, I added event handlers with which it is easy to track the state of the WebRTC session. You would do well to learn more about WebRTC connection flow, really.

Using Sockets.io with React.js + Hooks; sockets.emit Not Working

I am at a complete loss as to why this is not working, I am new to using Hooks in react however want to try.
This app basically connects sockets.io to the server using sockets-auth server emits the time every 1s <- this shows me that the connection is definitely live. It then uses sockets.emit to receive an array of data, this functions. The part that doesn't is a simple button press fails to achieve anything (see both 'Save Changes' buttons at bottom of React element). No error, no response, no acknowledgment at all. I have tried it inline, in its own function. The exact same server routes are functional simultaneously running a separate app with class components.
Help would be much appreciated. Anyway here is some code..
Client-Side (React.js)
import React, { Component,useState, useEffect } from "react";
import io from "socket.io-client";
import './Secret.css'
const Secret = () => {
const [time, setTime] = useState('');
const [userlist, setUserlist] = useState([]);
const socketUrl = 'http://localhost:3001';
let socket = io(socketUrl, {
autoConnect: false,
});
useEffect(() => {
const requestOptions = {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ title: 'React POST Request Example' })
};
fetch('/api/connectsocket', requestOptions)
.then(async response => {
const data = await response.json();
if (!response.ok) {
const error = (data && data.message) || response.status;
return Promise.reject(error);
}
console.log(data)
connectSocket(data)
})
.catch(error => {
console.error('There was an error!', error);
});
}, [socketUrl]);
const connectSocket= (data)=>{
let dataUser = data
console.log(dataUser.email)
let error = null;
socket.on('connect', () => {
console.log('Connected');
socket.emit('authentication', {
token: dataUser.email,
i: dataUser.i
});
});
socket.on('unauthorized', (reason) => {
console.log('Unauthorized:', reason);
error = reason.message;
socket.disconnect();
});
socket.on('disconnect', (reason) => {
console.log(`Disconnected: ${error || reason}`);
error = null;
});
socket.on("admin connected", data => {
socket.emit('admin userlist', { get:'x',for:'who' }, (error) => {
if(error) {
alert(error);
}
});
console.log(data)
});
socket.on("admin user list", data => {
setUserlist(data)
console.log(data)
});
socket.on("FromAPI", data => {
setTime(data)
console.log(data)
});
socket.open();
}
const sendMessage = (x)=>{
console.log(x)
socket.emit('hello', 'JSON.stringify(x)');
}
const doSomething = ()=>{
socket.emit('chatmessage', {msg:"Hello"});
}
return (
<div>
<div className="outerContainer">
<div className="container">
{time}
<button onClick={sendMessage}>Save Changes</button>
<button onClick={doSomething}>Save Changes</button>
{userlist.map(user => (
<tr key={user.id} value={user.id}>
<td>{user.email}</td>
<td>{user.email}</td>
<td>{user.email}</td>
</tr>
))}
</div>
</div>
</div>
);
}
export default Secret
Server-side (Node.js)
const http = require('http');
const cors = require('cors');
const express = require('express');
const bodyParser = require('body-parser');
const cookieParser = require('cookie-parser');
const io = require('socket.io')();
const socketAuth = require('socketio-auth');
//custom modules
const router = require('./modules/router');
const db = require('./modules/db.js');
const prettyDate = require('./modules/prettyDate.js');
const sortArray = require('./modules/sortArray.js');
const app = express();
const server = http.createServer(app);
io.attach(server);
app.use(cors()); ///delete for production
app.use(bodyParser.urlencoded({ extended: true }));
app.use(fileUpload({
createParentPath: true
}));
app.use(bodyParser.json());
app.use(cookieParser());
app.use(router);
app.use(express.static('public'))
db.i12345.sessions.persistence.setAutocompactionInterval(400)
////Sessions Store; not important if you want to run the code without
function setUser(id,user,i){
console.log('setuser')
let institution = i
db[institution].sessions.find({ user:user }, function (err, docs) {
if(docs.length){
db[institution].sessions.update({user:user }, { $set: { id: id } }, { multi: true }, function (err, numReplaced) {
});
} else {
var d = new Date();
var n = d.getTime();
var doc = { user: user
, id: id
, t:n
};
db[institution].sessions.insert(doc, function (err, newDoc) {
});
}
});
}
///user verification; could easily mock; if wanting to actually run code
async function verifyUser (token,i) {
var institution =i
return new Promise((resolve, reject) => {
// setTimeout to mock a cache or database call
setTimeout(() => {
var user = new Promise((resolve,reject) =>{
db[institution].users.find({email:token}, function (err, docs) {
if(docs.length){
resolve(docs);
} else {
reject(false)
}
});
});
if (!user) {
return reject('USER_NOT_FOUND');
}
return resolve(user);
}, 200);
});
}
//// Sockets auth implementation
socketAuth(io, {
authenticate: async (socket, data, callback) => {
const { token, i } = data;
console.log(data)
try {
const user = await verifyUser(token, i);
socket.user = user;
socket.i = i
console.log(i)
return callback(null, true);
} catch (e) {
console.log(`Socket ${socket.id} unauthorized.`);
return callback({ message: 'UNAUTHORIZED' });
}
},
postAuthenticate: async (socket) => {
console.log(`Socket ${socket.id} authenticated.`);
console.log(socket.user)
setUser(socket.id,socket.user, socket.i)
socket.emit('empty list', {queryList:true});
io.emit('admin connected', {admin:true});
socket.conn.on('packet', async (packet) => {
if (socket.auth && packet.type === 'ping') {
}
});
socket.on('chatmessage', (msg) => {
io.emit('chatmessage', msg);
console.log(msg);
});
socket.on('hello', (msg) => {
io.emit('chatmessage', msg);
console.log(msg);
});
socket.on('get tasks', (get) => {
let i = socket.i
let user = socket.user
getTasksChunk(get,i,user)
});
socket.on('admin userlist', (get) => {
let i = socket.i
let user = socket.user
adminGetUserList(get,i,user)
});
socket.on('admin roles', (data) => {
let i = socket.i
let user = socket.user
console.log(data)
console.log('reaced')
});
interval = setInterval(() => getApiAndEmit(socket), 1000);
},
disconnect: async (socket) => {
console.log(`Socket ${socket.id} disconnected.`);
if (socket.user) {
}
},
})
function getTasksChunk(get,i, user){
console.log(get,i,user);
let institution = i
db[institution].tasks24.find({}, async function (err, docs) {
if(docs.length){
for(i=0;i<docs.length;i++){
docs[i].location = `Ward: ${docs[i].location.Ward} Bed: ${docs[i].location.Bed}`
docs[i].patient = docs[i].patient.join(' | ')
docs[i].timestamp = prettyDate(new Date(+docs[i].timestamp))
}
console.log(docs)
let sorted = await sortArray(docs,'timestamp')
let chunk = sorted.slice(0,10)
io.emit('tasks in', chunk);
} else {
}
});
}
const getApiAndEmit = socket => {
const response = new Date();
// Emitting a new message. Will be consumed by the client
socket.emit("FromAPI", response);
};
///////ADMIN????????
function adminGetUserList(get,i,user){
var institution = i
console.log('hello')
db[institution].users.find({}, async function (err, docs) {
if(docs.length){
console.log(docs)
let sorted = await sortArray(docs,'timestamp')
io.emit('admin user list', sorted);
} else {
}
});
}
server.listen(process.env.PORT || 3001, () => console.log(`Server has started.`));
You must store your socket instance in a ref otherwise you would loose the connected socket instance when your component re-renders.
In short you need the socket reference to be the same across renders
const socketUrl = 'http://localhost:3001';
let socket = useRef(null);
useEffect(() => {
socket.current = io(socketUrl, {
autoConnect: false,
});
...
}, [socketUrl]);
Now note that whereever you are using socket you would use socket.current.
Ex:
socket,.current.on('hello', (msg) => {
io.emit('chatmessage', msg);
console.log(msg);
});

Node js Socket.iO accessing socket outside for multiple client

How can we access socket object outside for multiple socket connection. I created a object globally and tried to do this. But it always works for last connected socket.
'use strict';
const path = require('path')
const express = require('express');
const http = require('http');
const chalk = require('chalk');
const socketio = require('socket.io');
var connectionString = '';
const eventHubConsumerGroup = ""
const app = express()
const server = http.createServer(app ,() => {
console.log(chalk.green('Server created'))
})
const io = socketio(server)
const port = process.env.port || 3000
const publicDirectoryPath = path.join(__dirname , '../public')
var server_token = "1234567890";
app.use(express.static(publicDirectoryPath))
var localSocket;
io.on('connection',function(socket){
localSocket = socket;
console.log(socket.handshake.query.deviceID)
console.log('on user connected '+socket.id);
//report = new Report(socket);
socket.auth = false;
socket.on('authenticate',function(token){
console.log('token recieved is '+token);
if(server_token == token){
socket.auth = true;
console.log('connection is authenticated '+socket.id);
socket.emit("authenticate",true);
} else {
console.log("Connection not established")
socket.emit("authenticate",false);
}
})
socket.on('sendSocketEvent' , message => {
console.log(chalk.yellowBright(`Message recieved from ${socket.id} + ${message}`));
io.to(socket.id).emit('recieveSocketEvent', `Hello test`);
})
socket.on('disconnect',function(){
console.log('one user disconnected '+socket.id);
})
setTimeout(function(){
if(!socket.auth){
console.log('disconnecting the socket '+socket.id);
socket.emit("timeOut");
socket.disconnect();
}
},1000);
})
server.listen(port,() => {
console.log(chalk.redBright(`Server is up on port ${port}`))
})
var printMessage = function (message) {
console.log(JSON.stringify(message));
console.log(message.DeviceId);
if (localSocket != null){
if (message.DeviceId == localSocket.handshake.query.deviceID) {
localSocket.emit('recieveSocketEvent', message);
}
}
};
class EventHubReader {
constructor(connectionString, consumerGroup) {
this.connectionString = connectionString;
this.consumerGroup = consumerGroup;
this.eventHubClient = undefined;
this.receiveHandlers = undefined;
}
async startReadMessage(startReadMessageCallback) {
try {
console.log(this.connectionString)
const client = await EventHubClient.createFromIotHubConnectionString(this.connectionString);
console.log('Successfully created the EventHub Client from IoT Hub connection string.');
this.eventHubClient = client;
const partitionIds = await this.eventHubClient.getPartitionIds();
console.log('The partition ids are: ', partitionIds);
const onError = (err) => {
console.error(err.message || err);
};
const onMessage = (message) => {
const deviceId = message.annotations['iothub-connection-device-id'];
return startReadMessageCallback(message.body, message.enqueuedTimeUtc, deviceId);
};
this.receiveHandlers = partitionIds.map(id => this.eventHubClient.receive(id, onMessage, onError, {
eventPosition: EventPosition.fromEnqueuedTime(Date.now()),
consumerGroup: this.consumerGroup,
}));
} catch (ex) {
console.error(ex.message || ex);
}
}
// Close connection to Event Hub.
async stopReadMessage() {
const disposeHandlers = [];
this.receiveHandlers.forEach((receiveHandler) => {
disposeHandlers.push(receiveHandler.stop());
});
await Promise.all(disposeHandlers);
this.eventHubClient.close();
}
}
var { EventHubClient, EventPosition } = require('#azure/event-hubs');
const eventHubReader = new EventHubReader(connectionString, eventHubConsumerGroup);
(async () => {
console.log("Step1")
await eventHubReader.startReadMessage((message, date, deviceId) => {
console.log("Here getting called");
try {
const payload = {
IotData: message,
MessageDate: date || Date.now().toISOString(),
DeviceId: deviceId,
};
printMessage(payload);
} catch (err) {
console.error('Error broadcasting: [%s] from [%s].', err, message);
}
});
})().catch();
the problem is in condition "printMessage" . here I am trying to restrict the emit based on socket deviceID, but it's only working for last connected socket.
Can You please help me in this.
var localSocket;
io.on('connection',function(socket){
localSocket = socket;
})
You're overwriting the same variable, on each new connection, which means it will always point to the last socket connected.
What exactly do you want to do? To send this message to all connected sockets?

How to do Integration tests NodeJS + Firebase Admin?

I'm trying to write some integration tests on NodeJS with Firebase (firebase-admin, with the test library jest and supertest), and some tests are failing randomly when I run all my tests. Separately, my tests are passing, but it seems like when too many test cases are running, some api calls are failing. Does someone here already had such problem? What are the solutions for this problem? What could cause this problem? (NB: I run my tests sequentially for not mixing up the initialization of my database. I use the option --runInBand with jest)
There are some mocking libraries available, but it seems like they work with the old api of firebase.
Another solution would be to mock all my functions that manipulate firebase, but I won't have a "real" integration test anymore, and it means doing a lot of extra coding for writing those mock. Is it a best practice to do so?
Thank you in advance!
EDIT: code snippet:
initTest.js:
const request = require('supertest');
const net = require('net');
const app = require('../src/server').default;
export const initServer = () => {
const server = net.createServer(function(sock) {
sock.end('Hello world\n');
});
return server
}
export const createAdminAndReturnToken = async (password) => {
await request(app.callback())
.post('/admin/users/sa')
.set('auth','')
.send({password});
// user logs in
const res = await request(app.callback())
.post('/web/users/login')
.set('auth','')
.send({email:"sa#optimetriks.com",password})
return res.body.data.token;
}
utils.ts:
import firestore from "../../src/tools/firestore/index";
export async function execOperations(operations,action,obj) {
if (process.env.NODE_ENV === "test") {
await Promise.all(operations)
.then(() => {
console.log(action+" "+obj+" in database");
})
.catch(() => {
console.log("Error", "error while "+action+"ing "+obj+" to database");
});
} else {
console.log(
"Error",
"cannot execute this action outside from the test environment"
);
}
}
//////////////////////// Delete collections ////////////////////////
export async function deleteAllCollections() {
const collections = ["clients", "web_users","clients_web_users","clients_app_users","app_users"];
collections.forEach(collection => {
deleteCollection(collection);
});
}
export async function deleteCollection(collectionPath) {
const batchSize = 10;
var collectionRef = firestore.collection(collectionPath);
var query = collectionRef.orderBy("__name__").limit(batchSize);
return await new Promise((resolve, reject) => {
deleteQueryBatch(firestore, query, batchSize, resolve, reject);
});
}
async function deleteQueryBatch(firestore, query, batchSize, resolve, reject) {
query
.get()
.then(snapshot => {
// When there are no documents left, we are done
if (snapshot.size == 0) {
return 0;
}
// Delete documents in a batch
var batch = firestore.batch();
snapshot.docs.forEach(doc => {
batch.delete(doc.ref);
});
return batch.commit().then(() => {
return snapshot.size;
});
})
.then(numDeleted => {
if (numDeleted === 0) {
resolve();
return;
}
// Recurse on the next process tick, to avoid
// exploding the stack.
process.nextTick(() => {
deleteQueryBatch(firestore, query, batchSize, resolve, reject);
});
})
.catch(reject);
}
populateClient.ts:
import firestore from "../../src/tools/firestore/index";
import {execOperations} from "./utils";
import { generateClientData } from "../factory/clientFactory";
jest.setTimeout(10000); // some actions here needs more than the standard 5s timeout of jest
// CLIENT
export async function addClient(client) {
const clientData = await generateClientData(client);
await firestore
.collection("clients")
.doc(clientData.id)
.set(clientData)
}
export async function addClients(clientNb) {
let operations = [];
for (let i = 0; i < clientNb; i++) {
const clientData = await generateClientData({});
operations.push(
await firestore
.collection("clients")
.doc(clientData.id)
.set(clientData)
);
}
await execOperations(operations,"add","client");
}
retrieveClient.ts:
import firestore from "../../src/tools/firestore/index";
import { resolveSnapshotData } from "../../src/tools/tools";
export async function getAllClients() {
return new Promise((resolve, reject) => {
firestore
.collection("clients")
.get()
.then(data => {
resolveSnapshotData(data, resolve);
})
.catch(err => reject(err));
});
}
clients.test.js:
const request = require('supertest');
const app = require('../../../src/server').default;
const {deleteAllCollections, deleteCollection} = require('../../../__utils__/populate/utils')
const {addClient} = require('../../../__utils__/populate/populateClient')
const {getAllClients} = require('../../../__utils__/retrieve/retrieveClient')
const {initServer,createAdminAndReturnToken} = require('../../../__utils__/initTest');
const faker = require('faker');
let token_admin;
let _server;
// for simplicity, we use the same password for every users
const password = "secretpassword";
beforeAll(async () => {
_server = initServer(); // start
await deleteAllCollections()
// create a super admin, login and store the token
token_admin = await createAdminAndReturnToken(password);
_server.close(); // stop
})
afterAll(async () => {
// remove the users created during the campaign
_server = initServer(); // start
await deleteAllCollections()
_server.close(); // stop
})
describe('Manage client', () => {
beforeEach(() => {
_server = initServer(); // start
})
afterEach(async () => {
await deleteCollection("clients")
_server.close(); // stop
})
describe('Get All clients', () => {
const exec = (token) => {
return request(app.callback())
.get('/clients')
.set('auth',token)
}
it('should return a 200 when super admin provide the action', async () => {
const res = await exec(token_admin);
expect(res.status).toBe(200);
});
it('should contain an empty array while no client registered', async () => {
const res = await exec(token_admin);
expect(res.body.data.clients).toEqual([]);
});
it('should contain an array with one item while a client is registered', async () => {
// add a client
const clientId = faker.random.uuid();
await addClient({name:"client name",description:"client description",id:clientId})
// call get clients and check the result
const res = await exec(token_admin);
expect(res.body.data.clients.length).toBe(1);
expect(res.body.data.clients[0]).toHaveProperty('name','client name');
expect(res.body.data.clients[0]).toHaveProperty('description','client description');
expect(res.body.data.clients[0]).toHaveProperty('id',clientId);
});
})
describe('Get client by ID', () => {
const exec = (token,clientId) => {
return request(app.callback())
.get('/clients/' + clientId)
.set('auth',token)
}
it('should return a 200 when super admin provide the action', async () => {
const clientId = faker.random.uuid();
await addClient({id:clientId})
const res = await exec(token_admin,clientId);
expect(res.status).toBe(200);
});
it('should return a 404 when the client does not exist', async () => {
const nonExistingClientId = faker.random.uuid();
const res = await exec(token_admin,nonExistingClientId);
expect(res.status).toBe(404);
});
})
describe('Update client', () => {
const exec = (token,clientId,client) => {
return request(app.callback())
.patch('/clients/' + clientId)
.set('auth',token)
.send(client);
}
const clientModified = {
name:"name modified",
description:"description modified",
app_user_licenses: 15
}
it('should return a 200 when super admin provide the action', async () => {
const clientId = faker.random.uuid();
await addClient({id:clientId})
const res = await exec(token_admin,clientId,clientModified);
expect(res.status).toBe(200);
// check if the client id modified
let clients = await getAllClients();
expect(clients.length).toBe(1);
expect(clients[0]).toHaveProperty('name',clientModified.name);
expect(clients[0]).toHaveProperty('description',clientModified.description);
expect(clients[0]).toHaveProperty('app_user_licenses',clientModified.app_user_licenses);
});
it('should return a 404 when the client does not exist', async () => {
const nonExistingClientId = faker.random.uuid();
const res = await exec(token_admin,nonExistingClientId,clientModified);
expect(res.status).toBe(404);
});
})
describe('Create client', () => {
const exec = (token,client) => {
return request(app.callback())
.post('/clients')
.set('auth',token)
.send(client);
}
it('should return a 200 when super admin does the action', async () => {
const res = await exec(token_admin,{name:"clientA",description:"description for clientA"});
expect(res.status).toBe(200);
});
it('list of clients should be appended when a new client is created', async () => {
let clients = await getAllClients();
expect(clients.length).toBe(0);
const res = await exec(token_admin,{name:"clientA",description:"description for clientA"});
expect(res.status).toBe(200);
clients = await getAllClients();
expect(clients.length).toBe(1);
expect(clients[0]).toHaveProperty('name','clientA');
expect(clients[0]).toHaveProperty('description','description for clientA');
});
});
describe('Delete client', () => {
const exec = (token,clientId) => {
return request(app.callback())
.delete('/clients/'+ clientId)
.set('auth',token);
}
it('should return a 200 when super admin does the action', async () => {
const clientId = faker.random.uuid();
await addClient({id:clientId})
const res = await exec(token_admin,clientId);
expect(res.status).toBe(200);
});
it('should return a 404 when trying to delete a non-existing id', async () => {
const clientId = faker.random.uuid();
const nonExistingId = faker.random.uuid();
await addClient({id:clientId})
const res = await exec(token_admin,nonExistingId);
expect(res.status).toBe(404);
});
it('the client deleted should be removed from the list of clients', async () => {
const clientIdToDelete = faker.random.uuid();
const clientIdToRemain = faker.random.uuid();
await addClient({id:clientIdToRemain})
await addClient({id:clientIdToDelete})
let clients = await getAllClients();
expect(clients.length).toBe(2);
await exec(token_admin,clientIdToDelete);
clients = await getAllClients();
expect(clients.length).toBe(1);
expect(clients[0]).toHaveProperty('id',clientIdToRemain);
});
});
})
jest command: jest --coverage --forceExit --runInBand --collectCoverageFrom=src/**/*ts
I found the problem: I had a problem on the "deleteAllCollection" function, I forgot to put an "await".
Here is the correction for this function:
export async function deleteAllCollections() {
const collections = ["clients", "web_users","clients_web_users","clients_app_users","app_users"];
for (const collection of collections) {
await deleteCollection(collection);
};
}

Resources