Skip to content

Commit 3e94d80

Browse files
nanddeepnmilanholemans
authored andcommitted
Adds 'spo file sharinglink remove' command. Closes pnp#4027
1 parent 42524bc commit 3e94d80

File tree

5 files changed

+435
-0
lines changed

5 files changed

+435
-0
lines changed
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
# spo file sharinglink remove
2+
3+
Removes a specific sharing link of a file
4+
5+
## Usage
6+
7+
```sh
8+
m365 spo file sharinglink remove [options]
9+
```
10+
11+
## Options
12+
13+
`-u, --webUrl <webUrl>`
14+
: The URL of the site where the file is located.
15+
16+
`--fileUrl [fileUrl]`
17+
: The server-relative (decoded) URL of the file. Specify either `fileUrl` or `fileId` but not both.
18+
19+
`--fileId [fileId]`
20+
: The UniqueId (GUID) of the file. Specify either `fileUrl` or `fileId` but not both.
21+
22+
`-i, --id <id>`
23+
: The ID of the sharing link.
24+
25+
`--confirm`
26+
: Don't prompt for confirmation.
27+
28+
--8<-- "docs/cmd/_global.md"
29+
30+
## Examples
31+
32+
Removes a specific sharing link from a file by id without prompting for confirmation.
33+
34+
```sh
35+
m365 spo file sharinglink remove --webUrl https://contoso.sharepoint.com/sites/demo --fileId daebb04b-a773-4baa-b1d1-3625418e3234 --id c391b57d-5783-4c53-9236-cefb5c6ef323 --confirm
36+
```
37+
38+
Removes a specific sharing link from a file by a specified site-relative URL.
39+
40+
```sh
41+
m365 spo file sharinglink remove --webUrl https://contoso.sharepoint.com/sites/demo --fileUrl 'Shared Documents/document.docx' --id c391b57d-5783-4c53-9236-cefb5c6ef323
42+
```
43+
44+
Removes a specific sharing link from a file by a specified server-relative URL.
45+
46+
```sh
47+
m365 spo file sharinglink remove --webUrl https://contoso.sharepoint.com/sites/demo --fileUrl '/sites/demo/Shared Documents/document.docx' --id c391b57d-5783-4c53-9236-cefb5c6ef323
48+
```
49+
50+
## Response
51+
52+
The command won't return a response on success.

docs/mkdocs.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -403,6 +403,7 @@ nav:
403403
- file sharinginfo get: cmd/spo/file/file-sharinginfo-get.md
404404
- file sharinglink get: cmd/spo/file/file-sharinglink-get.md
405405
- file sharinglink list: cmd/spo/file/file-sharinglink-list.md
406+
- file sharinglink remove: cmd/spo/file/file-sharinglink-remove.md
406407
- file version clear: cmd/spo/file/file-version-clear.md
407408
- file version get: cmd/spo/file/file-version-get.md
408409
- file version list: cmd/spo/file/file-version-list.md

src/m365/spo/commands.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,7 @@ export default {
6363
FILE_SHARINGINFO_GET: `${prefix} file sharinginfo get`,
6464
FILE_SHARINGLINK_GET: `${prefix} file sharinglink get`,
6565
FILE_SHARINGLINK_LIST: `${prefix} file sharinglink list`,
66+
FILE_SHARINGLINK_REMOVE: `${prefix} file sharinglink remove`,
6667
FILE_VERSION_CLEAR: `${prefix} file version clear`,
6768
FILE_VERSION_GET: `${prefix} file version get`,
6869
FILE_VERSION_LIST: `${prefix} file version list`,
Lines changed: 221 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,221 @@
1+
import * as assert from 'assert';
2+
import * as sinon from 'sinon';
3+
import { telemetry } from '../../../../telemetry';
4+
import auth from '../../../../Auth';
5+
import { Cli } from '../../../../cli/Cli';
6+
import { CommandInfo } from '../../../../cli/CommandInfo';
7+
import { Logger } from '../../../../cli/Logger';
8+
import Command, { CommandError } from '../../../../Command';
9+
import request from '../../../../request';
10+
import { urlUtil } from '../../../../utils/urlUtil';
11+
import { formatting } from '../../../../utils/formatting';
12+
import { GraphFileDetails } from './GraphFileDetails';
13+
import { pid } from '../../../../utils/pid';
14+
import { sinonUtil } from '../../../../utils/sinonUtil';
15+
import commands from '../../commands';
16+
const command: Command = require('./file-sharinglink-remove');
17+
18+
describe(commands.FILE_SHARINGLINK_REMOVE, () => {
19+
const webUrl = 'https://contoso.sharepoint.com/sites/demo';
20+
const fileUrl = '/sites/demo/Shared Documents/document.docx';
21+
const fileId = 'daebb04b-a773-4baa-b1d1-3625418e3234';
22+
const id = 'U1BEZW1vIFZpc2l0b3Jz';
23+
24+
const fileInformationResponse: GraphFileDetails = {
25+
SiteId: '9798e615-a586-455e-8486-84913f492c49',
26+
VroomDriveID: 'b!FeaYl4alXkWEhoSRP0ksSSOaj9osSfFPqj5bQNdluvlwfL79GNVISZZCf6nfB3vY',
27+
VroomItemID: '01A5WCPNXHFAS23ZNOF5D3XU2WU7S3I2AU'
28+
};
29+
30+
let log: any[];
31+
let logger: Logger;
32+
let commandInfo: CommandInfo;
33+
let promptOptions: any;
34+
35+
before(() => {
36+
sinon.stub(auth, 'restoreAuth').callsFake(() => Promise.resolve());
37+
sinon.stub(telemetry, 'trackEvent').callsFake(() => { });
38+
sinon.stub(pid, 'getProcessName').callsFake(() => '');
39+
auth.service.connected = true;
40+
commandInfo = Cli.getCommandInfo(command);
41+
});
42+
43+
beforeEach(() => {
44+
log = [];
45+
logger = {
46+
log: (msg: string) => {
47+
log.push(msg);
48+
},
49+
logRaw: (msg: string) => {
50+
log.push(msg);
51+
},
52+
logToStderr: (msg: string) => {
53+
log.push(msg);
54+
}
55+
};
56+
sinon.stub(Cli, 'prompt').callsFake(async (options: any) => {
57+
promptOptions = options;
58+
return { continue: false };
59+
});
60+
promptOptions = undefined;
61+
});
62+
63+
afterEach(() => {
64+
sinonUtil.restore([
65+
request.get,
66+
request.delete,
67+
Cli.prompt
68+
]);
69+
});
70+
71+
after(() => {
72+
sinonUtil.restore([
73+
auth.restoreAuth,
74+
telemetry.trackEvent,
75+
pid.getProcessName
76+
]);
77+
auth.service.connected = false;
78+
});
79+
80+
it('has correct name', () => {
81+
assert.strictEqual(command.name, commands.FILE_SHARINGLINK_REMOVE);
82+
});
83+
84+
it('has a description', () => {
85+
assert.notStrictEqual(command.description, null);
86+
});
87+
88+
it('fails validation if the webUrl option is not a valid SharePoint site URL', async () => {
89+
const actual = await command.validate({ options: { webUrl: 'foo', fileId: fileId, id: id } }, commandInfo);
90+
assert.notStrictEqual(actual, true);
91+
});
92+
93+
it('passes validation if the webUrl option is a valid SharePoint site URL', async () => {
94+
const actual = await command.validate({ options: { webUrl: webUrl, fileId: fileId, id: id } }, commandInfo);
95+
assert.strictEqual(actual, true);
96+
});
97+
98+
it('fails validation if the fileId option is not a valid GUID', async () => {
99+
const actual = await command.validate({ options: { webUrl: webUrl, fileId: '12345', id: id } }, commandInfo);
100+
assert.notStrictEqual(actual, true);
101+
});
102+
103+
it('prompts before removing the specified sharing link to a file when confirm option not passed', async () => {
104+
await command.action(logger, {
105+
options: {
106+
webUrl: webUrl,
107+
fileId: fileId,
108+
id: id
109+
}
110+
});
111+
112+
let promptIssued = false;
113+
114+
if (promptOptions && promptOptions.type === 'confirm') {
115+
promptIssued = true;
116+
}
117+
118+
assert(promptIssued);
119+
});
120+
121+
it('aborts removing the specified sharing link to a file when confirm option not passed and prompt not confirmed', async () => {
122+
const deleteSpy = sinon.spy(request, 'delete');
123+
sinonUtil.restore(Cli.prompt);
124+
sinon.stub(Cli, 'prompt').callsFake(async () => (
125+
{ continue: false }
126+
));
127+
128+
await command.action(logger, {
129+
options: {
130+
webUrl: webUrl,
131+
fileUrl: fileUrl,
132+
id: id
133+
}
134+
});
135+
136+
assert(deleteSpy.notCalled);
137+
});
138+
139+
it('removes specified sharing link to a file by fileId when prompt confirmed', async () => {
140+
sinon.stub(request, 'get').callsFake(async (opts) => {
141+
if (opts.url === `${webUrl}/_api/web/GetFileById('${fileId}')?$select=SiteId,VroomItemId,VroomDriveId`) {
142+
return fileInformationResponse;
143+
}
144+
145+
throw 'Invalid request';
146+
});
147+
148+
const requestDeleteStub = sinon.stub(request, 'delete').callsFake(async (opts) => {
149+
if (opts.url === `https://graph.microsoft.com/v1.0/sites/${fileInformationResponse.SiteId}/drives/${fileInformationResponse.VroomDriveID}/items/${fileInformationResponse.VroomItemID}/permissions/${id}`) {
150+
return;
151+
}
152+
153+
throw 'Invalid request';
154+
});
155+
156+
sinonUtil.restore(Cli.prompt);
157+
sinon.stub(Cli, 'prompt').callsFake(async () => (
158+
{ continue: true }
159+
));
160+
161+
await command.action(logger, {
162+
options: {
163+
verbose: true,
164+
webUrl: webUrl,
165+
fileId: fileId,
166+
id: id
167+
}
168+
});
169+
assert(requestDeleteStub.called);
170+
});
171+
172+
it('removes specified sharing link to a file by URL', async () => {
173+
const fileServerRelativeUrl: string = urlUtil.getServerRelativePath(webUrl, fileUrl);
174+
sinon.stub(request, 'get').callsFake(async (opts) => {
175+
if (opts.url === `${webUrl}/_api/web/GetFileByServerRelativePath(decodedUrl='${formatting.encodeQueryParameter(fileServerRelativeUrl)}')?$select=SiteId,VroomItemId,VroomDriveId`) {
176+
return fileInformationResponse;
177+
}
178+
179+
throw 'Invalid request';
180+
});
181+
182+
const requestDeleteStub = sinon.stub(request, 'delete').callsFake(async (opts) => {
183+
if (opts.url === `https://graph.microsoft.com/v1.0/sites/${fileInformationResponse.SiteId}/drives/${fileInformationResponse.VroomDriveID}/items/${fileInformationResponse.VroomItemID}/permissions/${id}`) {
184+
return;
185+
}
186+
187+
throw 'Invalid request';
188+
});
189+
190+
await command.action(logger, {
191+
options: {
192+
verbose: true,
193+
webUrl: webUrl,
194+
fileUrl: fileUrl,
195+
id: id,
196+
confirm: true
197+
}
198+
});
199+
assert(requestDeleteStub.called);
200+
});
201+
202+
it('throws error when file not found by id', async () => {
203+
sinon.stub(request, 'get').callsFake(async (opts) => {
204+
if (opts.url === `${webUrl}/_api/web/GetFileById('${fileId}')?$select=SiteId,VroomItemId,VroomDriveId`) {
205+
throw { error: { 'odata.error': { message: { value: 'File Not Found.' } } } };
206+
}
207+
208+
throw 'Invalid request';
209+
});
210+
211+
await assert.rejects(command.action(logger, {
212+
options: {
213+
webUrl: webUrl,
214+
fileId: fileId,
215+
id: id,
216+
confirm: true,
217+
verbose: true
218+
}
219+
} as any), new CommandError(`File Not Found.`));
220+
});
221+
});

0 commit comments

Comments
 (0)