Skip to content

Commit f7c2295

Browse files
Saurabh7019MathijsVerbeeck
authored andcommitted
Adds command 'spo folder sharinglink set'. Closes #5964
1 parent 7af59ba commit f7c2295

File tree

5 files changed

+426
-0
lines changed

5 files changed

+426
-0
lines changed
Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
1+
import Global from '/docs/cmd/_global.mdx';
2+
import Tabs from '@theme/Tabs';
3+
import TabItem from '@theme/TabItem';
4+
5+
# spo folder sharinglink set
6+
7+
Updates a sharing link of a folder
8+
9+
## Usage
10+
11+
```sh
12+
m365 spo folder sharinglink set [options]
13+
```
14+
15+
## Options
16+
17+
```md definition-list
18+
`-u, --webUrl <webUrl>`
19+
: The URL of the site where the folder is located.
20+
21+
`--folderUrl [folderUrl]`
22+
: The server- or site-relative decoded URL of the folder. Specify either `folderUrl` or `folderId` but not both.
23+
24+
`--folderId [folderId]`
25+
: The unique ID (GUID) of the folder. Specify either `folderUrl` or `folderId` but not both.
26+
27+
`--id <id>`
28+
: The sharing link ID.
29+
30+
`--expirationDateTime <expirationDateTime>`
31+
: The date and time to set the expiration. This should be defined as a valid ISO 8601 string. This option only works for anonymous links.
32+
```
33+
34+
<Global />
35+
36+
## Examples
37+
38+
Updates the expiration datetime of an anonymous sharing link for a folder specific by folder ID.
39+
40+
```sh
41+
m365 spo folder sharinglink set --webUrl https://contoso.sharepoint.com/sites/demo --folderId daebb04b-a773-4baa-b1d1-3625418e3234 --id 7c9f97c9-1bda-433c-9364-bb83e81771ee --expirationDateTime '2022-11-30T00:00:00Z'
42+
```
43+
44+
Updates the expiration datetime of an anonymous sharing link for a folder specific by folder URL.
45+
46+
```sh
47+
m365 spo folder sharinglink set --webUrl https://contoso.sharepoint.com/sites/demo --folderUrl /sites/demo/shared%20documents/Folder --id 7c9f97c9-1bda-433c-9364-bb83e81771ee --expirationDateTime '2022-11-30T00:00:00Z'
48+
```
49+
50+
## Response
51+
52+
<Tabs>
53+
<TabItem value="JSON">
54+
55+
```json
56+
{
57+
"id": "bd1481e9-958b-4c1a-a33c-fb021f4ed444",
58+
"roles": [
59+
"write"
60+
],
61+
"shareId": "u!aHR0cHM6Ly83NTY2YXZhLnNoYXJlcG9pbnQuY29tLzpmOi9nL0VwLVpGUHF2YkVsQnNnWXJhRjJBNG1jQmZWM1A3cU00eGZVVXJRZHdnSXllNGc",
62+
"expirationDateTime": "2024-05-05T16:57:00Z",
63+
"hasPassword": false,
64+
"grantedToIdentitiesV2": [],
65+
"grantedToIdentities": [],
66+
"link": {
67+
"scope": "anonymous",
68+
"type": "edit",
69+
"webUrl": "https://contoso.sharepoint.com/:f:/g/Ep-ZFPqvbElBsgYraF2A4mcBfV3P7qM4xfUUrQdwgIye4g",
70+
"preventsDownload": false
71+
}
72+
}
73+
```
74+
75+
</TabItem>
76+
<TabItem value="Text">
77+
78+
```text
79+
expirationDateTime : 2024-05-05T16:57:00Z
80+
grantedToIdentities : []
81+
grantedToIdentitiesV2: []
82+
hasPassword : false
83+
id : bd1481e9-958b-4c1a-a33c-fb021f4ed444
84+
link : {"scope":"anonymous","type":"edit","webUrl":"https://contoso.sharepoint.com/:f:/g/Ep-ZFPqvbElBsgYraF2A4mcBfV3P7qM4xfUUrQdwgIye4g","preventsDownload":false}
85+
roles : ["write"]
86+
shareId : u!aHR0cHM6Ly83NTY2YXZhLnNoYXJlcG9pbnQuY29tLzpmOi9nL0VwLVpGUHF2YkVsQnNnWXJhRjJBNG1jQmZWM1A3cU00eGZVVXJRZHdnSXllNGc
87+
```
88+
89+
</TabItem>
90+
<TabItem value="CSV">
91+
92+
```csv
93+
id,shareId,expirationDateTime,hasPassword
94+
bd1481e9-958b-4c1a-a33c-fb021f4ed444,u!aHR0cHM6Ly83NTY2YXZhLnNoYXJlcG9pbnQuY29tLzpmOi9nL0VwLVpGUHF2YkVsQnNnWXJhRjJBNG1jQmZWM1A3cU00eGZVVXJRZHdnSXllNGc,2024-05-05T16:57:00Z,
95+
```
96+
97+
</TabItem>
98+
<TabItem value="Markdown">
99+
100+
```md
101+
# spo folder sharinglink set --webUrl "https://contoso.sharepoint.com" --folderUrl "/shared documents/f1" --id "bd1481e9-958b-4c1a-a33c-fb021f4ed444" --expirationDateTime "2024-05-05T16:57:00.000Z"
102+
103+
Date: 03/05/2024
104+
105+
## bd1481e9-958b-4c1a-a33c-fb021f4ed444
106+
107+
Property | Value
108+
---------|-------
109+
id | bd1481e9-958b-4c1a-a33c-fb021f4ed444
110+
shareId | u!aHR0cHM6Ly83NTY2YXZhLnNoYXJlcG9pbnQuY29tLzpmOi9nL0VwLVpGUHF2YkVsQnNnWXJhRjJBNG1jQmZWM1A3cU00eGZVVXJRZHdnSXllNGc
111+
expirationDateTime | 2024-05-05T16:57:00Z
112+
hasPassword | false
113+
```
114+
115+
</TabItem>
116+
</Tabs>

docs/src/config/sidebars.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2623,6 +2623,11 @@ const sidebars: SidebarsConfig = {
26232623
type: 'doc',
26242624
label: 'folder sharinglink remove',
26252625
id: 'cmd/spo/folder/folder-sharinglink-remove'
2626+
},
2627+
{
2628+
type: 'doc',
2629+
label: 'folder sharinglink set',
2630+
id: 'cmd/spo/folder/folder-sharinglink-set'
26262631
}
26272632
]
26282633
},

src/m365/spo/commands.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,7 @@ export default {
105105
FOLDER_SHARINGLINK_GET: `${prefix} folder sharinglink get`,
106106
FOLDER_SHARINGLINK_LIST: `${prefix} folder sharinglink list`,
107107
FOLDER_SHARINGLINK_REMOVE: `${prefix} folder sharinglink remove`,
108+
FOLDER_SHARINGLINK_SET: `${prefix} folder sharinglink set`,
108109
GET: `${prefix} get`,
109110
GROUP_ADD: `${prefix} group add`,
110111
GROUP_GET: `${prefix} group get`,
Lines changed: 176 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,176 @@
1+
import assert from 'assert';
2+
import sinon from 'sinon';
3+
import auth from '../../../../Auth.js';
4+
import { cli } from '../../../../cli/cli.js';
5+
import { CommandError } from '../../../../Command.js';
6+
import { telemetry } from '../../../../telemetry.js';
7+
import { Logger } from '../../../../cli/Logger.js';
8+
import request from '../../../../request.js';
9+
import { pid } from '../../../../utils/pid.js';
10+
import { session } from '../../../../utils/session.js';
11+
import { sinonUtil } from '../../../../utils/sinonUtil.js';
12+
import { CommandInfo } from '../../../../cli/CommandInfo.js';
13+
import commands from '../../commands.js';
14+
import command from './folder-sharinglink-set.js';
15+
import { spo } from '../../../../utils/spo.js';
16+
import { drive } from '../../../../utils/drive.js';
17+
import { Drive } from '@microsoft/microsoft-graph-types';
18+
19+
describe(commands.FOLDER_SHARINGLINK_SET, () => {
20+
let log: any[];
21+
let logger: Logger;
22+
let loggerLogSpy: sinon.SinonSpy;
23+
let commandInfo: CommandInfo;
24+
25+
const webUrl = 'https://contoso.sharepoint.com/sites/project-x';
26+
const folderId = 'f09c4efe-b8c0-4e89-a166-03418661b89b';
27+
const folderUrl = '/sites/project-x/shared documents/folder1';
28+
const siteId = '0f9b8f4f-0e8e-4630-bb0a-501442db9b64';
29+
const driveId = '013TMHP6UOOSLON57HT5GLKEU7R5UGWZVK';
30+
const itemId = 'b!T4-bD44OMEa7ClAUQtubZID9tc40pGJKpguycvELod_Gx-lo4ZQiRJ7vylonTufG';
31+
const id = 'ef1cddaa-b74a-4aae-8a7a-5c16b4da67f2';
32+
33+
const graphResponse = {
34+
"id": "2a021f54-90a2-4016-b3b3-5f34d2e7d932",
35+
"roles": [
36+
"read"
37+
],
38+
"hasPassword": false,
39+
"grantedToIdentitiesV2": [],
40+
"grantedToIdentities": [],
41+
"link": {
42+
"scope": "anonymous",
43+
"type": "view",
44+
"webUrl": "https://contoso.sharepoint.com/:b:/s/pnpcoresdktestgroup/EY50lub3559MtRKfj2hrZqoBWnHOpGIcgi4gzw9XiWYJ-A",
45+
"preventsDownload": false
46+
}
47+
};
48+
49+
const driveDetails: Drive = {
50+
id: driveId,
51+
webUrl: `${webUrl}/Shared%20Documents`
52+
};
53+
54+
const getStubs: any = (options: any) => {
55+
sinon.stub(spo, 'getFolderServerRelativeUrl').resolves(options.folderUrl);
56+
sinon.stub(spo, 'getSiteId').resolves(options.siteId);
57+
sinon.stub(drive, 'getDriveByUrl').resolves(options.drive);
58+
sinon.stub(drive, 'getDriveItemId').resolves(options.itemId);
59+
};
60+
61+
before(() => {
62+
sinon.stub(auth, 'restoreAuth').resolves();
63+
sinon.stub(telemetry, 'trackEvent').returns();
64+
sinon.stub(pid, 'getProcessName').returns('');
65+
sinon.stub(session, 'getId').returns('');
66+
auth.connection.active = true;
67+
commandInfo = cli.getCommandInfo(command);
68+
});
69+
70+
beforeEach(() => {
71+
log = [];
72+
logger = {
73+
log: async (msg: string) => {
74+
log.push(msg);
75+
},
76+
logRaw: async (msg: string) => {
77+
log.push(msg);
78+
},
79+
logToStderr: async (msg: string) => {
80+
log.push(msg);
81+
}
82+
};
83+
loggerLogSpy = sinon.spy(logger, 'log');
84+
});
85+
86+
afterEach(() => {
87+
sinonUtil.restore([
88+
request.get,
89+
request.patch,
90+
spo.getSiteId,
91+
spo.getFolderServerRelativeUrl,
92+
drive.getDriveByUrl,
93+
drive.getDriveItemId
94+
]);
95+
});
96+
97+
after(() => {
98+
sinon.restore();
99+
auth.connection.active = false;
100+
});
101+
102+
it('has correct name', () => {
103+
assert.strictEqual(command.name, commands.FOLDER_SHARINGLINK_SET);
104+
});
105+
106+
it('has a description', () => {
107+
assert.notStrictEqual(command.description, null);
108+
});
109+
110+
it('fails validation if the webUrl option is not a valid SharePoint site URL', async () => {
111+
const actual = await command.validate({ options: { webUrl: 'foo', folderId: folderId, id: id } }, commandInfo);
112+
assert.notStrictEqual(actual, true);
113+
});
114+
115+
it('fails validation if the folderId option is not a valid GUID', async () => {
116+
const actual = await command.validate({ options: { webUrl: webUrl, folderId: 'invalid', id: id } }, commandInfo);
117+
assert.notStrictEqual(actual, true);
118+
});
119+
120+
it('fails validation if the expirationDateTime option is not a valid date', async () => {
121+
const actual = await command.validate({ options: { webUrl: 'https://contoso.sharepoint.com', folderId: folderId, expirationDateTime: 'invalid date', id: id } }, commandInfo);
122+
assert.notStrictEqual(actual, true);
123+
});
124+
125+
it('passes validation if options are valid', async () => {
126+
const actual = await command.validate({ options: { webUrl: 'https://contoso.sharepoint.com', folderId: folderId, expirationDateTime: '2024-05-05T16:57:00.000Z', id: id } }, commandInfo);
127+
assert.strictEqual(actual, true);
128+
});
129+
130+
it('updates a sharing link to a folder specified by the id', async () => {
131+
getStubs({ folderUrl: folderUrl, siteId: siteId, drive: driveDetails, itemId: itemId });
132+
133+
sinon.stub(request, 'patch').callsFake(async (opts) => {
134+
if (opts.url === `https://graph.microsoft.com/v1.0/drives/${driveId}/items/${itemId}/permissions/${id}`) {
135+
return graphResponse;
136+
}
137+
138+
throw 'Invalid request';
139+
});
140+
141+
await command.action(logger, { options: { webUrl: webUrl, folderId: folderId, expirationDateTime: '2024-05-05T16:57:00.000Z', id: id, verbose: true } } as any);
142+
assert(loggerLogSpy.calledWith(graphResponse));
143+
});
144+
145+
it('updates a sharing link to a folder specified by the URL', async () => {
146+
getStubs({ folderUrl: folderUrl, siteId: siteId, drive: driveDetails, itemId: itemId });
147+
148+
sinon.stub(request, 'patch').callsFake(async (opts) => {
149+
if (opts.url === `https://graph.microsoft.com/v1.0/drives/${driveId}/items/${itemId}/permissions/${id}`) {
150+
return graphResponse;
151+
}
152+
153+
throw 'Invalid request';
154+
});
155+
156+
await command.action(logger, { options: { webUrl: webUrl, folderUrl: folderUrl, expirationDateTime: '2024-05-05T16:57:00.000Z', id: id, verbose: true } } as any);
157+
assert(loggerLogSpy.calledWith(graphResponse));
158+
});
159+
160+
it('throws error when drive not found by url', async () => {
161+
sinon.stub(spo, 'getFolderServerRelativeUrl').resolves(folderUrl);
162+
sinon.stub(spo, 'getSiteId').resolves(siteId);
163+
sinon.stub(request, 'get').callsFake(async opts => {
164+
if (opts.url === `https://graph.microsoft.com/v1.0/sites/${siteId}/drives?$select=webUrl,id`) {
165+
return {
166+
value: []
167+
};
168+
}
169+
170+
throw 'Invalid request';
171+
});
172+
173+
await assert.rejects(command.action(logger, { options: { webUrl: webUrl, folderUrl: folderUrl, expirationDateTime: '2024-05-05T16:57:00.000Z', id: id, verbose: true } } as any),
174+
new CommandError(`Drive 'https://contoso.sharepoint.com/sites/project-x/shared%20documents/folder1' not found`));
175+
});
176+
});

0 commit comments

Comments
 (0)