Skip to content

Commit

Permalink
Wait for file stream to close before resolving promise (#133)
Browse files Browse the repository at this point in the history
* Only resolve the promise once the writeStream has closed and finished

* Fix tests

* add create-packages

---------

Co-authored-by: Christian Holbrook <cholbrook@fubo.tv>
  • Loading branch information
TwitchBronBron and Christian-Holbrook authored Nov 30, 2023
1 parent 16ef61d commit daf1598
Show file tree
Hide file tree
Showing 6 changed files with 227 additions and 23 deletions.
3 changes: 2 additions & 1 deletion .eslintrc.js
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,7 @@ module.exports = {
'no-underscore-dangle': 'off',
'no-unneeded-ternary': 'off',
'no-useless-escape': 'off',
'no-void': 'off',
'no-warning-comments': 'off',
'object-curly-spacing': 'off',
'object-property-newline': 'off',
Expand Down Expand Up @@ -178,4 +179,4 @@ module.exports = {
}
],
ignorePatterns: ['types']
};
};
57 changes: 57 additions & 0 deletions .github/workflows/create-package.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
name: create-package
on:
pull_request:
types: [labeled, unlabeled, synchronize]
jobs:
create-package:
runs-on: ubuntu-latest
if: contains(github.event.pull_request.labels.*.name, 'create-package')
env:
GH_TOKEN: ${{ github.token }}
steps:
- uses: actions/checkout@master
- uses: actions/setup-node@master
with:
node-version: "14.19.0"
# Get a bot token so the bot's name shows up on all our actions
- name: Get Token From roku-ci-token Application
uses: tibdex/github-app-token@v1
id: generate-token
with:
app_id: ${{ secrets.BOT_APP_ID }}
private_key: ${{ secrets.BOT_PRIVATE_KEY }}
- run: echo "TOKEN=${{ steps.generate-token.outputs.token }}" >> $GITHUB_ENV
- name: Compute variables
run: |
CURRENT_VERSION=$(grep -o '\"version\": *\"[^\"]*\"' package.json | awk -F'\"' '{print $4}')
SANITIZED_BRANCH_NAME=$(echo "$GITHUB_HEAD_REF" | sed 's/[^0-9a-zA-Z-]/-/g')
BUILD_VERSION="$CURRENT_VERSION-$SANITIZED_BRANCH_NAME.$(date +%Y%m%d%H%M%S)"
NPM_PACKAGE_NAME=$(grep -o '\"name\": *\"[^\"]*\"' package.json | awk -F'\"' '{print $4}')
ARTIFACT_NAME=$(echo "$NPM_PACKAGE_NAME-$BUILD_VERSION.tgz" | tr '/' '-')
ARTIFACT_URL="${{ github.server_url }}/${{ github.repository }}/releases/download/v0.0.0-packages/${ARTIFACT_NAME}"
echo "BUILD_VERSION=$BUILD_VERSION" >> $GITHUB_ENV
echo "ARTIFACT_URL=$ARTIFACT_URL" >> $GITHUB_ENV
- run: npm ci
- run: npm version "$BUILD_VERSION" --no-git-tag-version
- run: npm pack

# create the release if not exist
- run: gh release create v0.0.0-packages --latest=false --prerelease --notes "catchall release for temp packages" -R ${{ github.repository }}
continue-on-error: true

# upload this artifact to the "packages" github release
- run: gh release upload v0.0.0-packages *.tgz -R ${{ github.repository }}

- name: Fetch build artifact
uses: actions/github-script@v7
with:
github-token: ${{ env.TOKEN }}
script: |
return github.rest.issues.createComment({
issue_number: context.issue.number,
owner: context.repo.owner,
repo: context.repo.repo,
body: "Hey there! I just built a new temporary npm package based on ${{ github.event.pull_request.head.sha }}. You can download it [here](${{ env.ARTIFACT_URL }}) or install it by running the following command: \n```bash\nnpm install ${{ env.ARTIFACT_URL }}\n```"
});
30 changes: 30 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@
"@types/micromatch": "^4.0.2",
"@types/mocha": "^9.0.0",
"@types/node": "^16.11.3",
"@types/q": "^1.5.8",
"@types/request": "^2.47.0",
"@types/sinon": "^10.0.4",
"@types/xml2js": "^0.4.5",
Expand All @@ -52,6 +53,7 @@
"eslint": "8.0.1",
"mocha": "^9.1.3",
"nyc": "^15.1.0",
"q": "^1.5.1",
"rimraf": "^2.6.2",
"sinon": "^11.1.2",
"source-map-support": "^0.5.13",
Expand Down
122 changes: 109 additions & 13 deletions src/RokuDeploy.spec.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
import * as assert from 'assert';
import { expect } from 'chai';
import * as fsExtra from 'fs-extra';
import type { WriteStream, PathLike } from 'fs-extra';
import * as fs from 'fs';
import * as q from 'q';
import * as path from 'path';
import * as JSZip from 'jszip';
import * as child_process from 'child_process';
Expand All @@ -12,14 +15,20 @@ import { util, standardizePath as s } from './util';
import type { FileEntry, RokuDeployOptions } from './RokuDeployOptions';
import { cwd, expectPathExists, expectPathNotExists, expectThrowsAsync, outDir, rootDir, stagingDir, tempDir, writeFiles } from './testUtils.spec';
import { createSandbox } from 'sinon';
import * as request from 'postman-request';
import * as r from 'postman-request';
import type * as requestType from 'request';
const request = r as typeof requestType;

const sinon = createSandbox();

describe('index', () => {
let rokuDeploy: RokuDeploy;
let options: RokuDeployOptions;

let writeStreamPromise: Promise<WriteStream>;
let writeStreamDeferred: q.Deferred<WriteStream> & { isComplete: undefined | true };
let createWriteStreamStub: sinon.SinonStub;

beforeEach(() => {
rokuDeploy = new RokuDeploy();
options = rokuDeploy.getOptions({
Expand All @@ -38,9 +47,24 @@ describe('index', () => {
fsExtra.ensureDirSync(stagingDir);
//most tests depend on a manifest file existing, so write an empty one
fsExtra.outputFileSync(`${rootDir}/manifest`, '');

writeStreamDeferred = q.defer<WriteStream>() as any;
writeStreamPromise = writeStreamDeferred.promise as any;

//fake out the write stream function
createWriteStreamStub = sinon.stub(rokuDeploy.fsExtra, 'createWriteStream').callsFake((filePath: PathLike) => {
const writeStream = fs.createWriteStream(filePath);
writeStreamDeferred.resolve(writeStream);
writeStreamDeferred.isComplete = true;
return writeStream;
});
});

afterEach(() => {
if (createWriteStreamStub.called && !writeStreamDeferred.isComplete) {
writeStreamDeferred.reject('Deferred was never resolved...so rejecting in the afterEach');
}

sinon.restore();
//restore the original working directory
process.chdir(cwd);
Expand Down Expand Up @@ -1873,13 +1897,8 @@ describe('index', () => {
describe('takeScreenshot', () => {
let onHandler: any;
let screenshotAddress: any;

beforeEach(() => {
//fake out the write stream function
sinon.stub(rokuDeploy.fsExtra, 'createWriteStream').callsFake((filePath: any) => {
fsExtra.outputFileSync(filePath, 'test');
screenshotAddress = filePath;
return null;
});

//intercept the http request
sinon.stub(request, 'get').callsFake(() => {
Expand All @@ -1890,7 +1909,11 @@ describe('index', () => {
});
return req;
},
pipe: () => { }
pipe: async () => {
const writeStream = await writeStreamPromise;
writeStream.write(Buffer.from('test-content'));
writeStream.close();
}
};
return req;
});
Expand Down Expand Up @@ -3001,9 +3024,6 @@ describe('index', () => {
//do nothing, assume the dir gets created
}) as any);

//fake out the write stream function
sinon.stub(rokuDeploy.fsExtra, 'createWriteStream').returns(null);

//intercept the http request
sinon.stub(request, 'get').callsFake(() => {
let req: any = {
Expand All @@ -3013,12 +3033,17 @@ describe('index', () => {
});
return req;
},
pipe: () => { }
pipe: async () => {
//if a write stream gets created, write some stuff and close it
const writeStream = await writeStreamPromise;
writeStream.write('test');
writeStream.close();
}
};
return req;
});

});

it('returns a pkg file path on success', async () => {
onHandler = (event, callback) => {
if (event === 'response') {
Expand All @@ -3033,6 +3058,31 @@ describe('index', () => {
expect(pkgFilePath).to.equal(path.join(process.cwd(), 'out', 'roku-deploy-test.pkg'));
});

it('returns a pkg file path on success', async () => {
//the write stream should return null, which causes a specific branch to be executed
createWriteStreamStub.callsFake(() => {
return null;
});

onHandler = (event, callback) => {
if (event === 'response') {
callback({
statusCode: 200
});
}
};

let error: Error;
try {
await rokuDeploy.retrieveSignedPackage('path_to_pkg', {
outFile: 'roku-deploy-test'
});
} catch (e) {
error = e as any;
}
expect(error.message.startsWith('Unable to create write stream for')).to.be.true;
});

it('throws when error in request is encountered', async () => {
onHandler = (event, callback) => {
if (event === 'error') {
Expand Down Expand Up @@ -3353,6 +3403,52 @@ describe('index', () => {
});
});

describe('getToFile', () => {
it('waits for the write stream to finish writing before resolving', async () => {
let getToFileIsResolved = false;

let requestCalled = q.defer();
let onResponse = q.defer<(res) => any>();

//intercept the http request
sinon.stub(request, 'get').callsFake(() => {
requestCalled.resolve();
let req: any = {
on: (event, callback) => {
if (event === 'response') {
onResponse.resolve(callback);
}
return req;
},
pipe: () => {
return req;
}
};
return req;
});

const finalPromise = rokuDeploy['getToFile']({}, s`${tempDir}/out/something.txt`).then(() => {
getToFileIsResolved = true;
});

await requestCalled.promise;
expect(getToFileIsResolved).to.be.false;

const callback = await onResponse.promise;
callback({ statusCode: 200 });
await util.sleep(10);

expect(getToFileIsResolved).to.be.false;

const writeStream = await writeStreamPromise;
writeStream.write('test');
writeStream.close();

await finalPromise;
expect(getToFileIsResolved).to.be.true;
});
});

describe('deployAndSignPackage', () => {
beforeEach(() => {
//pretend the deploy worked
Expand Down
Loading

0 comments on commit daf1598

Please sign in to comment.