Https.request response.pipe ile sinon.js ile söz ve olası yollar nasıl saplanır?

Nov 09 2020

Şu anda https.request response.pipe sinon.js ile nasıl bağlanır? Bölümünde anlatılana benzer bir şey yapıyorum.

Aslında ben de aynısını yapıyorum ama kodum biraz daha karmaşık çünkü istek kodunun işlenmesiyle ilgili birden çok yol (koşul) var ve geri arama yerine bir söz kullanıyorum. Ayrıca istek modülü yerine https kullanıyorum.

Şu anda çalışamadığım aşağıdaki koda sahibim:

//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');
                })
        });
    });

Diğer gönderideki kodu gereksinimlerime uyacak şekilde nasıl uyarlayacağımdan emin değilim.

İstek kodunu ve ardından akışı içeren bir yanıtı nasıl göndereceğimi ve ayrıca sözün o zaman testte nasıl işleneceğini bilmiyorum.

Yardımın için gerçekten minnettar olurum.

Yanıtlar

1 eol Nov 09 2020 at 21:13

Akış nesnesine ekleme statusCodeveya headersözellikler gibi alay verileriniz üzerinde daha fazla kontrole sahip olmak için özel bir yazılabilir akış oluşturabilirsiniz . Bunun gibi bir şey yaratırdım (bu , okunacak hiçbir şey kalmayana kadar _read()verilenlerden 4 baytlık veriyi yayacak gerçek bir akışı simüle eder responseBody:

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)
        }
    }
}

Daha sonra bunu birim testinizde şu şekilde kullanabilirsiniz:

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);
    });
});

fetchArchiveBunun çalışması için bir bit uygulamasını değiştirmeniz gerektiğini unutmayın :

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))
                }
            })
    });
}