From 52ec5a11f46c07f94f226319d45d6180e8500958 Mon Sep 17 00:00:00 2001 From: Seth MacLean Date: Tue, 21 Jan 2025 17:07:21 -0400 Subject: [PATCH 1/3] fixed an issue with 577 error codes --- src/RokuDeploy.spec.ts | 29 +++++++++++++++++++++++++++-- src/RokuDeploy.ts | 2 ++ 2 files changed, 29 insertions(+), 2 deletions(-) diff --git a/src/RokuDeploy.spec.ts b/src/RokuDeploy.spec.ts index a05e50a..3e46c46 100644 --- a/src/RokuDeploy.spec.ts +++ b/src/RokuDeploy.spec.ts @@ -1208,13 +1208,38 @@ describe('index', () => { assert.fail('Should not have succeeded'); }); - it('Should throw an excpetion', async () => { + it('Should throw an exception and call doPost once', async () => { + options.failOnCompileError = true; + let spy = mockDoPostRequest('', 577); + + try { + await rokuDeploy.publish(options); + } catch (e) { + expect(spy.callCount).to.eql(1); + assert.ok('Exception was thrown as expected'); + expect(e).to.be.instanceof(errors.UpdateCheckRequiredError); + return; + } + assert.fail('Should not have succeeded'); + }); + + it('Should throw an exception and should call doPost twice', async () => { options.failOnCompileError = true; - mockDoPostRequest('', 577); + let spy = sinon.stub(rokuDeploy as any, 'doPostRequest').callsFake((params: any) => { + let results: any; + if (params?.formData['mysubmit'] === 'Replace') { + results = { response: { statusCode: 500 }, body: `'not an update error'` }; + } else { + results = { response: { statusCode: 577 }, body: `` }; + } + rokuDeploy['checkRequest'](results); + return Promise.resolve(results); + }); try { await rokuDeploy.publish(options); } catch (e) { + expect(spy.callCount).to.eql(2); assert.ok('Exception was thrown as expected'); expect(e).to.be.instanceof(errors.UpdateCheckRequiredError); return; diff --git a/src/RokuDeploy.ts b/src/RokuDeploy.ts index 6a734dd..ab5bce3 100644 --- a/src/RokuDeploy.ts +++ b/src/RokuDeploy.ts @@ -468,6 +468,8 @@ export class RokuDeploy { //fail if this is a compile error if (this.isCompileError(replaceError.message) && options.failOnCompileError) { throw new errors.CompileError('Compile error', replaceError, replaceError.results); + } else if (replaceError?.results?.response?.statusCode === 577 || (typeof replaceError?.results?.body === 'string' && this.isUpdateCheckRequiredResponse(replaceError.results.body))) { + throw replaceError; } else { requestOptions.formData.mysubmit = 'Install'; response = await this.doPostRequest(requestOptions); From b3fca6d2da7bb29a0914f05713da6160dee8d473 Mon Sep 17 00:00:00 2001 From: Seth MacLean Date: Tue, 21 Jan 2025 17:21:37 -0400 Subject: [PATCH 2/3] increased test coverage --- src/RokuDeploy.spec.ts | 50 +++++++++++++++++++++++++++++++++++++++++- src/RokuDeploy.ts | 2 +- 2 files changed, 50 insertions(+), 2 deletions(-) diff --git a/src/RokuDeploy.spec.ts b/src/RokuDeploy.spec.ts index 3e46c46..f1d3c68 100644 --- a/src/RokuDeploy.spec.ts +++ b/src/RokuDeploy.spec.ts @@ -1103,7 +1103,7 @@ describe('index', () => { }); }); - it('rejects when response contains invalid password status code', async () => { + it('rejects when response contains update device messaging', async () => { options.failOnCompileError = true; mockDoPostRequest(`'Failed to check for software update'`, 200); @@ -1117,6 +1117,54 @@ describe('index', () => { } }); + it('rejects when response contains update device messaging and bad status code on first call', async () => { + options.failOnCompileError = true; + let spy = sinon.stub(rokuDeploy as any, 'doPostRequest').callsFake((params: any) => { + let results: any; + if (params?.formData['mysubmit'] === 'Replace') { + results = { response: { statusCode: 500 }, body: `'Failed to check for software update'` }; + } else { + results = { response: { statusCode: 200 }, body: `` }; + } + rokuDeploy['checkRequest'](results); + return Promise.resolve(results); + }); + + try { + await rokuDeploy.publish(options); + assert.fail('Should not have succeeded due to roku server compilation failure'); + } catch (err) { + expect(spy.callCount).to.eql(1); + expect((err as any).message).to.eql( + errors.UpdateCheckRequiredError.MESSAGE + ); + } + }); + + it('rejects when response contains update device messaging and bad status code on second call', async () => { + options.failOnCompileError = true; + let spy = sinon.stub(rokuDeploy as any, 'doPostRequest').callsFake((params: any) => { + let results: any; + if (params?.formData['mysubmit'] === 'Replace') { + results = { response: { statusCode: 500 }, body: `` }; + } else { + results = { response: { statusCode: 500 }, body: `'Failed to check for software update'` }; + } + rokuDeploy['checkRequest'](results); + return Promise.resolve(results); + }); + + try { + await rokuDeploy.publish(options); + assert.fail('Should not have succeeded due to roku server compilation failure'); + } catch (err) { + expect(spy.callCount).to.eql(2); + expect((err as any).message).to.eql( + errors.UpdateCheckRequiredError.MESSAGE + ); + } + }); + it('handles successful deploy', () => { options.failOnCompileError = true; mockDoPostRequest(); diff --git a/src/RokuDeploy.ts b/src/RokuDeploy.ts index ab5bce3..a6e0bbf 100644 --- a/src/RokuDeploy.ts +++ b/src/RokuDeploy.ts @@ -477,7 +477,7 @@ export class RokuDeploy { } } catch (e: any) { //if this is a 577 error, we have high confidence that the device needs to do an update check - if (e.results?.response?.statusCode === 577) { + if (e.results?.response?.statusCode === 577 || (typeof e?.results?.body === 'string' && this.isUpdateCheckRequiredResponse(e.results.body))) { throw new errors.UpdateCheckRequiredError(response, requestOptions, e); //a reset connection could be cause by several things, but most likely it's due to the device needing to check for updates From ce1d3750b52a97b7ad7a9bf6aee3ae6db02dddfc Mon Sep 17 00:00:00 2001 From: Christopher Dwyer-Perkins Date: Tue, 21 Jan 2025 18:15:06 -0400 Subject: [PATCH 3/3] Get tests back to full covrage --- src/RokuDeploy.spec.ts | 40 ++++++++++++++++++++++++++++++++++++++++ src/RokuDeploy.ts | 11 +++++++++-- 2 files changed, 49 insertions(+), 2 deletions(-) diff --git a/src/RokuDeploy.spec.ts b/src/RokuDeploy.spec.ts index f1d3c68..b50cd6b 100644 --- a/src/RokuDeploy.spec.ts +++ b/src/RokuDeploy.spec.ts @@ -3771,6 +3771,46 @@ describe('index', () => { }); }); + describe('isUpdateRequiredError', () => { + it('returns true if the status code is 577', () => { + expect( + rokuDeploy['isUpdateRequiredError']({ results: { response: { statusCode: 577 } } }) + ).to.be.true; + }); + + it('returns true if the body is an update response from device', () => { + const response = `\n\n \n \n Roku Development Kit \n\n \n\n\n
\n\n
\n\n \n \n\n\n
\n\n Please make sure that your Roku device is connected to internet, and running most recent software version (d=953108)\n\n
\n\n\n\n`; + expect( + rokuDeploy['isUpdateRequiredError']({ results: { response: { statusCode: 500 }, body: response } }) + ).to.be.true; + }); + + it('returns false on missing results', () => { + expect( + rokuDeploy['isUpdateRequiredError']({ }) + ).to.be.false; + }); + + it('returns false on missing response', () => { + expect( + rokuDeploy['isUpdateRequiredError']({ results: {} }) + ).to.be.false; + }); + + it('returns false on missing status code', () => { + expect( + rokuDeploy['isUpdateRequiredError']({ results: { response: {} } }) + ).to.be.false; + }); + + it('returns false on non-string missing body', () => { + expect( + rokuDeploy['isUpdateRequiredError']({ results: { response: { statusCode: 500 }, body: false } }) + ).to.be.false; + }); + + }); + function mockDoGetRequest(body = '', statusCode = 200) { return sinon.stub(rokuDeploy as any, 'doGetRequest').callsFake((params) => { let results = { response: { statusCode: statusCode }, body: body }; diff --git a/src/RokuDeploy.ts b/src/RokuDeploy.ts index a6e0bbf..26ec742 100644 --- a/src/RokuDeploy.ts +++ b/src/RokuDeploy.ts @@ -468,7 +468,7 @@ export class RokuDeploy { //fail if this is a compile error if (this.isCompileError(replaceError.message) && options.failOnCompileError) { throw new errors.CompileError('Compile error', replaceError, replaceError.results); - } else if (replaceError?.results?.response?.statusCode === 577 || (typeof replaceError?.results?.body === 'string' && this.isUpdateCheckRequiredResponse(replaceError.results.body))) { + } else if (this.isUpdateRequiredError(replaceError)) { throw replaceError; } else { requestOptions.formData.mysubmit = 'Install'; @@ -477,7 +477,7 @@ export class RokuDeploy { } } catch (e: any) { //if this is a 577 error, we have high confidence that the device needs to do an update check - if (e.results?.response?.statusCode === 577 || (typeof e?.results?.body === 'string' && this.isUpdateCheckRequiredResponse(e.results.body))) { + if (this.isUpdateRequiredError(e)) { throw new errors.UpdateCheckRequiredError(response, requestOptions, e); //a reset connection could be cause by several things, but most likely it's due to the device needing to check for updates @@ -531,6 +531,13 @@ export class RokuDeploy { return !!/["']\s*Failed\s*to\s*check\s*for\s*software\s*update\s*["']/i.exec(responseHtml); } + /** + * Checks to see if the exception is due to the device needing to check for updates + */ + private isUpdateRequiredError(e: any): boolean { + return e.results?.response?.statusCode === 577 || (typeof e.results?.body === 'string' && this.isUpdateCheckRequiredResponse(e.results.body)); + } + /** * Converts existing loaded package to squashfs for faster loading packages * @param options