Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: fix issue when deploy fails when images specified as file paths … #272

Merged
merged 2 commits into from
Aug 4, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
82 changes: 22 additions & 60 deletions lib/controllers/skill-infrastructure-controller/index.js
Original file line number Diff line number Diff line change
@@ -1,16 +1,15 @@
const R = require('ramda');
const path = require('path');

const SmapiClient = require('@src/clients/smapi-client');
const SkillMetadataController = require('@src/controllers/skill-metadata-controller');
const ResourcesConfig = require('@src/model/resources-config');
const Manifest = require('@src/model/manifest');
const jsonView = require('@src/view/json-view');
const MultiTasksView = require('@src/view/multi-tasks-view');
const Messenger = require('@src/view/messenger');
const retryUtils = require('@src/utils/retry-utility');
const hashUtils = require('@src/utils/hash-utils');
const stringUtils = require('@src/utils/string-utils');
const CONSTANTS = require('@src/utils/constants');
const SpinnerView = require('@src/view/spinner-view');
const profileHelper = require('@src/utils/profile-helper');

const DeployDelegate = require('./deploy-delegate');

Expand Down Expand Up @@ -157,7 +156,7 @@ module.exports = class SkillInfrastructureController {
if (currentHash === ResourcesConfig.getInstance().getSkillMetaLastDeployHash(this.profile)) {
return callback();
}
// 3.update skill manifest
// 3.re-upload skill package
this._ensureSkillManifestGotUpdated((manifestUpdateErr) => {
if (manifestUpdateErr) {
return callback(manifestUpdateErr);
Expand Down Expand Up @@ -230,63 +229,26 @@ module.exports = class SkillInfrastructureController {
}

/**
* Make sure the skill manifest is updated successfully by submitting the request to SMAPI and keep polling until complete.
* Make sure the skill manifest is updated successfully by deploying the skill package
* @param {Function} callback
*/
_ensureSkillManifestGotUpdated(callback) {
const smapiClient = new SmapiClient({ profile: this.profile, doDebug: this.doDebug });
const skillId = ResourcesConfig.getInstance().getSkillId(this.profile);
// update manifest
smapiClient.skill.manifest.updateManifest(skillId, CONSTANTS.SKILL.STAGE.DEVELOPMENT, Manifest.getInstance().content, null,
(updateErr, updateResponse) => {
if (updateErr) {
return callback(updateErr);
}
if (updateResponse.statusCode >= 300) {
return callback(jsonView.toString(updateResponse.body));
}
// poll manifest status until finish
this._pollSkillStatus(smapiClient, skillId, (pollErr, pollResponse) => {
if (pollErr) {
return callback(pollErr);
}
const manifestStatus = R.view(R.lensPath(['body', 'manifest', 'lastUpdateRequest', 'status']), pollResponse);
if (!manifestStatus) {
return callback(`[Error]: Failed to extract the manifest result from SMAPI's response.\n${pollResponse}`);
}
if (manifestStatus !== CONSTANTS.SKILL.SKILL_STATUS.SUCCEEDED) {
return callback(`[Error]: Updating skill manifest but received non-success message from SMAPI: ${manifestStatus}`);
}
callback();
});
});
}

/**
* Poll skill's manifest status until the status is not IN_PROGRESS.
* @param {Object} smapiClient
* @param {String} skillId
* @param {Function} callback
*/
_pollSkillStatus(smapiClient, skillId, callback) {
const retryConfig = {
base: 1000,
factor: 1.1,
maxRetry: 50
};
const retryCall = (loopCallback) => {
smapiClient.skill.getSkillStatus(skillId, [CONSTANTS.SKILL.RESOURCES.MANIFEST], (statusErr, statusResponse) => {
if (statusErr) {
return loopCallback(statusErr);
}
if (statusResponse.statusCode >= 300) {
return loopCallback(jsonView.toString(statusResponse.body));
}
loopCallback(null, statusResponse);
});
};
const shouldRetryCondition = retryResponse => R.view(R.lensPath(['body', 'manifest', 'lastUpdateRequest', 'status']), retryResponse)
=== CONSTANTS.SKILL.SKILL_STATUS.IN_PROGRESS;
retryUtils.retry(retryConfig, retryCall, shouldRetryCondition, (err, res) => callback(err, err ? null : res));
const spinner = new SpinnerView();
spinner.start('Updating skill package from the skill infrastructure deploy results.');
let vendorId, skillMetaController;
try {
vendorId = profileHelper.resolveVendorId(this.profile);
skillMetaController = new SkillMetadataController({ profile: this.profile, doDebug: this.doDebug });
} catch (err) {
spinner.terminate();
return callback(err);
}
skillMetaController.deploySkillPackage(vendorId, this.ignoreHash, (deployErr) => {
spinner.terminate();
if (deployErr) {
return callback(deployErr);
}
callback();
});
}
};
174 changes: 11 additions & 163 deletions test/unit/controller/skill-infrastructure-controller-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,24 +4,21 @@ const fs = require('fs');
const fse = require('fs-extra');
const path = require('path');

const httpClient = require('@src/clients/http-client');
const SmapiClient = require('@src/clients/smapi-client');
const SkillMetadataController = require('@src/controllers/skill-metadata-controller');
const ResourcesConfig = require('@src/model/resources-config');
const Manifest = require('@src/model/manifest');
const SkillInfrastructureController = require('@src/controllers/skill-infrastructure-controller');
const DeployDelegate = require('@src/controllers/skill-infrastructure-controller/deploy-delegate');
const MultiTasksView = require('@src/view/multi-tasks-view');
const jsonView = require('@src/view/json-view');
const AuthorizationController = require('@src/controllers/authorization-controller');
const profileHelper = require('@src/utils/profile-helper');
const hashUtils = require('@src/utils/hash-utils');
const CONSTANTS = require('@src/utils/constants');

describe('Controller test - skill infrastructure controller test', () => {
const FIXTURE_RESOURCES_CONFIG_FILE_PATH = path.join(process.cwd(), 'test', 'unit', 'fixture', 'model', 'regular-proj', 'ask-resources.json');
const FIXTURE_MANIFEST_FILE_PATH = path.join(process.cwd(), 'test', 'unit', 'fixture', 'model', 'manifest.json');
const TEST_PROFILE = 'default'; // test file uses 'default' profile
const TEST_WORKSPACE = 'workspace';
const TEST_SKILL_ID = 'skillId';
const TEST_CONFIGURATION = {
profile: TEST_PROFILE,
doDebug: false
Expand Down Expand Up @@ -573,10 +570,10 @@ describe('Controller test - skill infrastructure controller test', () => {
sinon.restore();
});

it('| SMAPI update manifest connection fails, expect error called back', (done) => {
it('| deploy skill package fails, expect error called back', (done) => {
// setup
sinon.stub(AuthorizationController.prototype, 'tokenRefreshAndRead').callsArgWith(1);
sinon.stub(httpClient, 'request').callsArgWith(3, 'error');
sinon.stub(profileHelper, 'resolveVendorId');
sinon.stub(SkillMetadataController.prototype, 'deploySkillPackage').yields('error');
// call
skillInfraController._ensureSkillManifestGotUpdated((err, res) => {
// verify
Expand All @@ -586,105 +583,21 @@ describe('Controller test - skill infrastructure controller test', () => {
});
});

it('| SMAPI update manifest fails with >= 300 error code, expect SMAPI error called back', (done) => {
// setup
const TEST_SMAPI_RESPONSE = {
statusCode: 401,
body: {
message: 'unauthrized'
}
};
sinon.stub(AuthorizationController.prototype, 'tokenRefreshAndRead').callsArgWith(1);
sinon.stub(httpClient, 'request').callsArgWith(3, null, TEST_SMAPI_RESPONSE);
// call
skillInfraController._ensureSkillManifestGotUpdated((err, res) => {
// verify
expect(res).equal(undefined);
expect(err).equal(jsonView.toString(TEST_SMAPI_RESPONSE.body));
done();
});
});

it('| SMAPI update manifest passes but polling fails, expect polling error called back', (done) => {
// setup
const TEST_SMAPI_RESPONSE = {
statusCode: 202
};
sinon.stub(AuthorizationController.prototype, 'tokenRefreshAndRead').callsArgWith(1);
sinon.stub(httpClient, 'request').callsArgWith(3, null, TEST_SMAPI_RESPONSE);
sinon.stub(SkillInfrastructureController.prototype, '_pollSkillStatus').callsArgWith(2, 'poll error');
// call
skillInfraController._ensureSkillManifestGotUpdated((err, res) => {
// verify
expect(res).equal(undefined);
expect(err).equal('poll error');
done();
});
});

it('| SMAPI update manifest passes but polling cause SMAPI to fail, expect SMAPI error called back', (done) => {
it('| resolve vendor id fails, expect error called back', (done) => {
// setup
const TEST_SMAPI_RESPONSE = {
statusCode: 202
};
const TEST_POLL_RESPONSE = {
body: 'invalid'
};
sinon.stub(AuthorizationController.prototype, 'tokenRefreshAndRead').callsArgWith(1);
sinon.stub(httpClient, 'request').callsArgWith(3, null, TEST_SMAPI_RESPONSE);
sinon.stub(SkillInfrastructureController.prototype, '_pollSkillStatus').callsArgWith(2, null, TEST_POLL_RESPONSE);
sinon.stub(profileHelper, 'resolveVendorId').throws(new Error('error'));
// call
skillInfraController._ensureSkillManifestGotUpdated((err, res) => {
// verify
expect(res).equal(undefined);
expect(err.startsWith('[Error]: Failed to extract the manifest result from SMAPI\'s response.\n')).equal(true);
expect(err.message).equal('error');
done();
});
});

it('| SMAPI update manifest passes but polling result is not SUCCEEDED, expect SMAPI response errored back', (done) => {
// setup
const TEST_SMAPI_RESPONSE = {
statusCode: 202
};
const TEST_POLL_RESPONSE = {
body: {
manifest: {
lastUpdateRequest: {
status: 'TEST_STATUS'
}
}
}
};
sinon.stub(AuthorizationController.prototype, 'tokenRefreshAndRead').callsArgWith(1);
sinon.stub(httpClient, 'request').callsArgWith(3, null, TEST_SMAPI_RESPONSE);
sinon.stub(SkillInfrastructureController.prototype, '_pollSkillStatus').callsArgWith(2, null, TEST_POLL_RESPONSE);
// call
skillInfraController._ensureSkillManifestGotUpdated((err, res) => {
// verify
expect(res).equal(undefined);
expect(err).equal('[Error]: Updating skill manifest but received non-success message from SMAPI: TEST_STATUS');
done();
});
});

it('| SMAPI update manifest passes and update succeeds, expect call back with no error', (done) => {
// setup
const TEST_SMAPI_RESPONSE = {
statusCode: 202
};
const TEST_POLL_RESPONSE = {
body: {
manifest: {
lastUpdateRequest: {
status: CONSTANTS.SKILL.SKILL_STATUS.SUCCEEDED
}
}
}
};
sinon.stub(AuthorizationController.prototype, 'tokenRefreshAndRead').callsArgWith(1);
sinon.stub(httpClient, 'request').callsArgWith(3, null, TEST_SMAPI_RESPONSE);
sinon.stub(SkillInfrastructureController.prototype, '_pollSkillStatus').callsArgWith(2, null, TEST_POLL_RESPONSE);
it('| deploy skill package succeeds, expect call back with no error', (done) => {
sinon.stub(profileHelper, 'resolveVendorId');
sinon.stub(SkillMetadataController.prototype, 'deploySkillPackage').yields();
// call
skillInfraController._ensureSkillManifestGotUpdated((err, res) => {
// verify
Expand All @@ -694,69 +607,4 @@ describe('Controller test - skill infrastructure controller test', () => {
});
});
});

describe('# test class method: _pollSkillStatus', () => {
const testSmapiClient = new SmapiClient(TEST_CONFIGURATION);
const skillInfraController = new SkillInfrastructureController(TEST_CONFIGURATION);

afterEach(() => {
sinon.restore();
});

it('| poll skill status but error happens when polling status', (done) => {
// setup
sinon.stub(AuthorizationController.prototype, 'tokenRefreshAndRead').callsArgWith(1);
sinon.stub(httpClient, 'request').callsArgWith(3, 'error');
// call
skillInfraController._pollSkillStatus(testSmapiClient, TEST_SKILL_ID, (err, res) => {
// verify
expect(res).equal(null);
expect(err).equal('error');
done();
});
});

it('| poll skill status but SMAPI returns failure', (done) => {
// setup
const TEST_SMAPI_RESPONSE = {
statusCode: 401,
body: {
message: 'unauthrized'
}
};
sinon.stub(AuthorizationController.prototype, 'tokenRefreshAndRead').callsArgWith(1);
sinon.stub(httpClient, 'request').callsArgWith(3, null, TEST_SMAPI_RESPONSE);
// call
skillInfraController._pollSkillStatus(testSmapiClient, TEST_SKILL_ID, (err, res) => {
// verify
expect(res).equal(null);
expect(err).equal(jsonView.toString(TEST_SMAPI_RESPONSE.body));
done();
});
});

it('| poll skill status successfully complete', (done) => {
// setup
const TEST_SMAPI_RESPONSE = {
statusCode: 202,
body: {
manifest: {
lastUpdateRequest: {
status: 'TEST'
}
}
}
};
sinon.stub(AuthorizationController.prototype, 'tokenRefreshAndRead').callsArgWith(1);
sinon.stub(httpClient, 'request').callsArgWith(3, null, TEST_SMAPI_RESPONSE);
// call
skillInfraController._pollSkillStatus(testSmapiClient, TEST_SKILL_ID, (err, res) => {
// verify
expect(err).equal(null);
expect(res.statusCode).equal(202);
expect(res.body).deep.equal(TEST_SMAPI_RESPONSE.body);
done();
});
});
});
});