Skip to content

Commit

Permalink
Added the 'spo hubsite theme sync' command solving #401
Browse files Browse the repository at this point in the history
  • Loading branch information
Robert Jaakke authored and waldekmastykarz committed Apr 9, 2018
1 parent 9e627b5 commit 6997478
Show file tree
Hide file tree
Showing 5 changed files with 455 additions and 0 deletions.
41 changes: 41 additions & 0 deletions docs/manual/docs/cmd/spo/hubsite/hubsite-theme-sync.md
@@ -0,0 +1,41 @@
# spo hubsite theme sync

Applies any theme updates from the parent hub site.

!!! attention
This command is based on a SharePoint API that is currently in preview and is subject to change once the API reached general availability.

## Usage

```sh
spo hubsite theme sync [options]
```

## Options

Option|Description
------|-----------
`--help`|output usage information
`-u, --webUrl <webUrl>`|URL of the site to apply theme updates from the hub site to.
`-o, --output [output]`|Output type. `json|text`. Default `text`
`--verbose`|Runs command with verbose logging
`--debug`|Runs command with debug logging

!!! important
Before using this command, connect to a SharePoint Online site, using the [spo connect](../connect.md) command.

## Remarks

To apply hub site theme updates to a site, you have to first connect to a SharePoint site using the [spo connect](../connect.md) command, eg. `spo connect https://contoso.sharepoint.com`.

## Examples

Applies any theme updates from the parent hub site to the site with URL https://contoso.sharepoint.com/sites/project-x

```sh
spo hubsite theme sync --webUrl https://contoso.sharepoint.com/sites/project-x
```

## More information

- SharePoint hub sites new in Office 365: [https://techcommunity.microsoft.com/t5/SharePoint-Blog/SharePoint-hub-sites-new-in-Office-365/ba-p/109547](https://techcommunity.microsoft.com/t5/SharePoint-Blog/SharePoint-hub-sites-new-in-Office-365/ba-p/109547)
1 change: 1 addition & 0 deletions docs/manual/mkdocs.yml
Expand Up @@ -52,6 +52,7 @@ pages:
- hubsite rights grant: 'cmd/spo/hubsite/hubsite-rights-grant.md'
- hubsite rights revoke: 'cmd/spo/hubsite/hubsite-rights-revoke.md'
- hubsite set: 'cmd/spo/hubsite/hubsite-set.md'
- hubsite theme sync: 'cmd/spo/hubsite/hubsite-theme-sync.md'
- hubsite unregister: 'cmd/spo/hubsite/hubsite-unregister.md'
- list:
- list add: 'cmd/spo/list/list-add.md'
Expand Down
1 change: 1 addition & 0 deletions src/o365/spo/commands.ts
Expand Up @@ -38,6 +38,7 @@ export default {
HUBSITE_RIGHTS_GRANT: `${prefix} hubsite rights grant`,
HUBSITE_RIGHTS_REVOKE: `${prefix} hubsite rights revoke`,
HUBSITE_SET: `${prefix} hubsite set`,
HUBSITE_THEME_SYNC: `${prefix} hubsite theme sync`,
HUBSITE_UNREGISTER: `${prefix} hubsite unregister`,
LIST_ADD: `${prefix} list add`,
LIST_GET: `${prefix} list get`,
Expand Down
273 changes: 273 additions & 0 deletions src/o365/spo/commands/hubsite/hubsite-theme-sync.spec.ts
@@ -0,0 +1,273 @@
import commands from '../../commands';
import Command, { CommandOption, CommandValidate, CommandError } from '../../../../Command';
import * as sinon from 'sinon';
import appInsights from '../../../../appInsights';
import auth, { Site } from '../../SpoAuth';
const command: Command = require('./hubsite-theme-sync');
import * as assert from 'assert';
import * as request from 'request-promise-native';
import Utils from '../../../../Utils';

describe(commands.HUBSITE_THEME_SYNC, () => {
let vorpal: Vorpal;
let log: string[];
let cmdInstance: any;
let cmdInstanceLogSpy: sinon.SinonSpy;
let trackEvent: any;
let telemetry: any;

before(() => {
sinon.stub(auth, 'restoreAuth').callsFake(() => Promise.resolve());
sinon.stub(auth, 'getAccessToken').callsFake(() => { return Promise.resolve('ABC'); });
sinon.stub(command as any, 'getRequestDigestForSite').callsFake(() => Promise.resolve({ FormDigestValue: 'ABC' }));
trackEvent = sinon.stub(appInsights, 'trackEvent').callsFake((t) => {
telemetry = t;
});
});

beforeEach(() => {
vorpal = require('../../../../vorpal-init');
log = [];
cmdInstance = {
log: (msg: string) => {
log.push(msg);
}
};
cmdInstanceLogSpy = sinon.spy(cmdInstance, 'log');
auth.site = new Site();
telemetry = null;
});

afterEach(() => {
Utils.restore([
vorpal.find,
request.post
]);
});

after(() => {
Utils.restore([
appInsights.trackEvent,
auth.getAccessToken,
auth.restoreAuth,
]);
});

it('has correct name', () => {
assert.equal(command.name.startsWith(commands.HUBSITE_THEME_SYNC), true);
});

it('has a description', () => {
assert.notEqual(command.description, null);
});

it('calls telemetry', (done) => {
cmdInstance.action = command.action();
cmdInstance.action({ options: {} }, () => {
try {
assert(trackEvent.called);
done();
}
catch (e) {
done(e);
}
});
});

it('logs correct telemetry event', (done) => {
cmdInstance.action = command.action();
cmdInstance.action({ options: {} }, () => {
try {
assert.equal(telemetry.name, commands.HUBSITE_THEME_SYNC);
done();
}
catch (e) {
done(e);
}
});
});

it('aborts when not connected to a SharePoint site', (done) => {
auth.site = new Site();
auth.site.connected = false;
cmdInstance.action = command.action();
cmdInstance.action({ options: { debug: true } }, () => {
try {
assert(cmdInstanceLogSpy.calledWith(new CommandError('Connect to a SharePoint Online site first')));
done();
}
catch (e) {
done(e);
}
});
});

it('syncs hub site theme to a web', (done) => {
sinon.stub(request, 'post').callsFake((opts) => {
if (opts.url.indexOf(`/_api/web/SyncHubSiteTheme`) > -1) {
return Promise.resolve({ "odata.null": true });
}

return Promise.reject('Invalid request');
});

auth.site = new Site();
auth.site.connected = true;
auth.site.url = 'https://contoso.sharepoint.com';
cmdInstance.action = command.action();
cmdInstance.action({ options: { debug: false, webUrl: 'https://contoso.sharepoint.com/sites/Project-X' } }, () => {
try {
assert(cmdInstanceLogSpy.notCalled);
done();
}
catch (e) {
done(e);
}
});
});

it('syncs hub site theme to a web (debug)', (done) => {
sinon.stub(request, 'post').callsFake((opts) => {
if (opts.url.indexOf(`/_api/web/SyncHubSiteTheme`) > -1) {
return Promise.resolve({
"odata.null": true
});
}

return Promise.reject('Invalid request');
});

auth.site = new Site();
auth.site.connected = true;
auth.site.url = 'https://contoso.sharepoint.com';
cmdInstance.action = command.action();
cmdInstance.action({ options: { debug: true, webUrl: 'https://contoso.sharepoint.com/sites/Project-X' } }, () => {
try {
assert(cmdInstanceLogSpy.calledWith(vorpal.chalk.green('DONE')));
done();
}
catch (e) {
done(e);
}
});
});

it('correctly handles error when hub site not found', (done) => {
sinon.stub(request, 'post').callsFake((opts) => {
return Promise.reject({
error: {
"odata.error": {
"code": "-1, Microsoft.SharePoint.Client.ResourceNotFoundException",
"message": {
"lang": "en-US",
"value": "Exception of type 'Microsoft.SharePoint.Client.ResourceNotFoundException' was thrown."
}
}
}
});
});

auth.site = new Site();
auth.site.connected = true;
auth.site.url = 'https://contoso.sharepoint.com';
cmdInstance.action = command.action();
cmdInstance.action({ options: { debug: false, webUrl: 'https://contoso.sharepoint.com/sites/Project-X' } }, () => {
try {
assert(cmdInstanceLogSpy.calledWith(new CommandError("Exception of type 'Microsoft.SharePoint.Client.ResourceNotFoundException' was thrown.")));
done();
}
catch (e) {
done(e);
}
});
});

it('supports debug mode', () => {
const options = (command.options() as CommandOption[]);
let containsOption = false;
options.forEach(o => {
if (o.option === '--debug') {
containsOption = true;
}
});
assert(containsOption);
});

it('supports specifying webUrl', () => {
const options = (command.options() as CommandOption[]);
let containsOption = false;
options.forEach(o => {
if (o.option.indexOf('--webUrl') > -1) {
containsOption = true;
}
});
assert(containsOption);
});

it('fails validation if webUrl is not specified', () => {
const actual = (command.validate() as CommandValidate)({ options: { id: '9b142c22-037f-4a7f-9017-e9d8c0e34b99' } });
assert.notEqual(actual, true);
});

it('fails validation if webUrl is not a valid SharePoint URL', () => {
const actual = (command.validate() as CommandValidate)({ options: { id: '9b142c22-037f-4a7f-9017-e9d8c0e34b99', webUrl: 'Invalid' } });
assert.notEqual(actual, true);
});

it('passed validation if webUrl is a valid SharePoint URL', () => {
const actual = (command.validate() as CommandValidate)({ options: { id: '9b142c22-037f-4a7f-9017-e9d8c0e34b99', webUrl: 'https://contoso.sharepoint.com' } });
assert.equal(actual, true);
});

it('has help referring to the right command', () => {
const cmd: any = {
log: (msg: string) => { },
prompt: () => { },
helpInformation: () => { }
};
const find = sinon.stub(vorpal, 'find').callsFake(() => cmd);
cmd.help = command.help();
cmd.help({}, () => { });
assert(find.calledWith(commands.HUBSITE_THEME_SYNC));
});

it('has help with examples', () => {
const _log: string[] = [];
const cmd: any = {
log: (msg: string) => {
_log.push(msg);
},
prompt: () => { },
helpInformation: () => { }
};
sinon.stub(vorpal, 'find').callsFake(() => cmd);
cmd.help = command.help();
cmd.help({}, () => { });
let containsExamples: boolean = false;
_log.forEach(l => {
if (l && l.indexOf('Examples:') > -1) {
containsExamples = true;
}
});
Utils.restore(vorpal.find);
assert(containsExamples);
});

it('correctly handles lack of valid access token', (done) => {
Utils.restore(auth.getAccessToken);
sinon.stub(auth, 'getAccessToken').callsFake(() => { return Promise.reject(new Error('Error getting access token')); });
auth.site = new Site();
auth.site.connected = true;
auth.site.url = 'https://contoso-admin.sharepoint.com';
cmdInstance.action = command.action();
cmdInstance.action({ options: { debug: true, webUrl: 'https://contoso.sharepoint.com/sites/Sales', confirm: true } }, () => {
try {
assert(cmdInstanceLogSpy.calledWith(new CommandError('Error getting access token')));
done();
}
catch (e) {
done(e);
}
});
});
});

0 comments on commit 6997478

Please sign in to comment.