Como criar um stub de https.request response.pipe com sinon.js e promessa e caminhos possíveis?
No momento, estou fazendo algo semelhante ao descrito em Como criar stub https.request response.pipe com sinon.js?
Na verdade, estou fazendo o mesmo, mas meu código é um pouco mais complexo, pois tenho vários caminhos (condições) em relação ao tratamento do código de solicitação e uso uma promessa em vez de retorno de chamada. Além disso, uso o https em vez do módulo de solicitação.
Atualmente, tenho o seguinte código que não consigo fazer funcionar:
//utils.js
/**
* Fetches the repository archive
*
* @param url The archive download url
* @param dest The temp directory path
* @param accessToken The access token for the repository (only available if private repository)
*/
exports.fetchArchive = function(url, dest, accessToken) {
let options = {
headers: {}
}
if (accessToken) {
options.headers = {'PRIVATE-TOKEN': accessToken}
}
return new Promise((resolve, reject) => {
https
.get(url, options,(response) => {
const code = response.statusCode;
if (code >= 400) {
reject({ code, message: response.statusMessage });
} else if (code >= 300) {
this.fetchArchive(response.headers.location, dest).then(resolve, reject);
} else {
response
.pipe(fs.createWriteStream(dest))
.on('end', () => resolve(null))
.on('error', () => reject({ code, message: response.statusMessage }));
}
})
});
}
_
//utils.test.js
describe('public fetchArchive', () => {
it(`should have a redirect status code (>= 300) and redirect and thus be called at twice`, () => {
let options = {
headers: {}
}
options.headers = {'PRIVATE-TOKEN': repoPropsPrivate.accessToken}
const mockResponse = `{"data": 123}`;
// //Using a built-in PassThrough stream to emit needed data.
const mockStream = new PassThrough();
mockStream.push(mockResponse);
mockStream.end(); //Mark that we pushed all the data.
sinon
.stub(https, 'get')
.callsFake(function (privateUrl, options, callback) {
callback(mockStream);
return Promise.resolve(null); //Stub end method btw
});
//Finally keep track of how 'pipe' is going to be called
sinon.spy(mockStream, 'pipe');
return utils.fetchArchive(privateArchiveUrl, privateArchiveDest, repoPropsPrivate.accessToken)
.then((res) => {
sinon.assert.calledOnce(mockStream.pipe);
//We can get the stream that we piped to.
let writable = mockStream.pipe.getCall(0).args[0];
assert.equal(writable.path, './output.json');
})
});
});
Não tenho certeza de como adaptar o código da outra postagem para atender aos meus requisitos.
Não sei como enviar uma resposta incluindo o código de solicitação seguido pelo fluxo e, além disso, como processar a promessa então no teste.
Eu realmente apreciaria sua ajuda.
Respostas
Você poderia criar um fluxo gravável customizado para ter mais controle sobre seus dados simulados, como adicionar propriedades statusCodeou headersno objeto de fluxo. Eu criaria algo assim (isso simula um fluxo real onde _read()emitirá 4 bytes de dados do dado responseBodyaté que não haja mais nada para ler:
const {Readable} = require('stream');
class ResponseStreamMock extends Readable {
constructor(statusCode, responseBody) {
super();
this.statusCode = statusCode;
this.headers = {location: 'someLocation'};
this.responseData = responseBody !== undefined ? Buffer.from(JSON.stringify(responseBody), "utf8") : Buffer.from([]);
this.bytesRead = 0;
this.offset = 4;
}
_read() {
if (this.bytesRead >= this.responseData.byteLength) {
this.push(null);
} else {
setTimeout(() => {
const buff = this.responseData.toString('utf8', this.bytesRead, this.bytesRead + this.offset);
this.push(Buffer.from(buff));
this.bytesRead += this.offset;
}, Math.random() * 200)
}
}
}
Você pode então usá-lo assim em seu teste de unidade:
describe('public fetchArchive', () => {
it(`should redirect for status code >= 300, then pipe response into fs-stream`, async function () {
this.timeout(5000)
const httpGetStub = sinon.stub(https, 'get');
let redirectStream = new ResponseStreamMock(300);
let responseStream = new ResponseStreamMock(200, {"someData": {"test": 123}});
httpGetStub.onCall(0).callsFake(function (privateUrl, options, callback) {
redirectStream.resume(); // immediately flush the stream as this one does not get piped
callback(redirectStream);
});
httpGetStub.onCall(1).callsFake(function (privateUrl, options, callback) {
callback(responseStream);
});
sinon.spy(redirectStream, 'pipe');
sinon.spy(responseStream, 'pipe');
const fsWriteStreamStub = sinon
.stub(fs, 'createWriteStream').callsFake(() => process.stdout); // you can replace process.stdout with new PassThrough() if you don't want any output
const result = await fetchArchive("someURL", "someDestination", "")
sinon.assert.calledOnce(fsWriteStreamStub);
sinon.assert.calledOnce(responseStream.pipe);
sinon.assert.notCalled(redirectStream.pipe);
assert.equal(result, null);
});
});
Observe que você precisa alterar fetchArchiveum pouco a implementação para fazer isso funcionar:
exports.fetchArchive = function fetchArchive(url, dest, accessToken) {
let options = {
headers: {}
}
if (accessToken) {
options.headers = {'PRIVATE-TOKEN': accessToken}
}
return new Promise((resolve, reject) => {
https
.get(url, options, (response) => {
const code = response.statusCode;
if (code >= 400) {
reject({code, message: response.statusMessage});
} else if (code >= 300) {
fetchArchive(response.headers.location, dest).then(resolve, reject);
} else {
response.on('end', () => resolve(null))
response.on('error', () => reject({code, message: response.statusMessage}));
response.pipe(fs.createWriteStream(dest))
}
})
});
}