Skip to content

Commit ba79c61

Browse files
authored
Adds command 'spo site versionpolicy get'. Closes #6750
1 parent 2a451b5 commit ba79c61

File tree

6 files changed

+402
-15
lines changed

6 files changed

+402
-15
lines changed

.devproxy/api-specs/sharepoint.yaml

Lines changed: 38 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,29 @@ paths:
7979
responses:
8080
200:
8181
description: OK
82+
/_api/site/VersionPolicyForNewLibrariesTemplate:
83+
get:
84+
parameters:
85+
- name: $expand
86+
in: query
87+
required: false
88+
schema:
89+
type: string
90+
example: VersionPolicies
91+
security:
92+
- delegated:
93+
- AllSites.Read
94+
- AllSites.Write
95+
- AllSites.Manage
96+
- AllSites.FullControl
97+
- application:
98+
- Sites.Read.All
99+
- Sites.ReadWrite.All
100+
- Sites.Manage.All
101+
- Sites.FullControl.All
102+
responses:
103+
200:
104+
description: OK
82105
/_api/web:
83106
get:
84107
security:
@@ -160,15 +183,15 @@ paths:
160183
example: "'19bbfec4-4425-4660-95cb-da1887baa7b9'"
161184
security:
162185
- delegated:
163-
- AllSites.Read
164-
- AllSites.Write
165-
- AllSites.Manage
166-
- AllSites.FullControl
186+
- AllSites.Read
187+
- AllSites.Write
188+
- AllSites.Manage
189+
- AllSites.FullControl
167190
- application:
168-
- Sites.Read.All
169-
- Sites.ReadWrite.All
170-
- Sites.Manage.All
171-
- Sites.FullControl.All
191+
- Sites.Read.All
192+
- Sites.ReadWrite.All
193+
- Sites.Manage.All
194+
- Sites.FullControl.All
172195
responses:
173196
200:
174197
description: OK
@@ -189,13 +212,13 @@ paths:
189212
example: 1030
190213
security:
191214
- delegated:
192-
- AllSites.Write
193-
- AllSites.Manage
194-
- AllSites.FullControl
215+
- AllSites.Write
216+
- AllSites.Manage
217+
- AllSites.FullControl
195218
- application:
196-
- Sites.ReadWrite.All
197-
- Sites.Manage.All
198-
- Sites.FullControl.All
219+
- Sites.ReadWrite.All
220+
- Sites.Manage.All
221+
- Sites.FullControl.All
199222
responses:
200223
200:
201224
description: OK
@@ -322,4 +345,4 @@ paths:
322345
description: OK
323346
x-ms-generated-by:
324347
toolName: Dev Proxy
325-
toolVersion: 0.25.0
348+
toolVersion: 0.25.0
Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
import Global from '/docs/cmd/_global.mdx';
2+
import Tabs from '@theme/Tabs';
3+
import TabItem from '@theme/TabItem';
4+
5+
# spo site versionpolicy get
6+
7+
Retrieves the version policy settings of a specific site.
8+
9+
## Usage
10+
11+
```sh
12+
m365 spo site versionpolicy get [options]
13+
```
14+
15+
## Options
16+
17+
```md definition-list
18+
`-u, --siteUrl <siteUrl>`
19+
: URL of the site.
20+
```
21+
22+
<Global />
23+
24+
## Permissions
25+
26+
<Tabs>
27+
<TabItem value="Delegated">
28+
29+
| Resource | Permissions |
30+
|------------|---------------|
31+
| SharePoint | AllSites.Read |
32+
33+
</TabItem>
34+
<TabItem value="Application">
35+
36+
| Resource | Permissions |
37+
|------------|----------------|
38+
| SharePoint | Sites.Read.All |
39+
40+
</TabItem>
41+
</Tabs>
42+
43+
## Examples
44+
45+
Retrieve the version policy settings of a specific site.
46+
47+
```sh
48+
m365 spo site versionpolicy get --siteUrl "https://contoso.sharepoint.com/sites/Marketing"
49+
```
50+
51+
## Response
52+
53+
<Tabs>
54+
<TabItem value="JSON">
55+
56+
```json
57+
{
58+
"defaultTrimMode": "automatic",
59+
"defaultExpireAfterDays": 30,
60+
"majorVersionLimit": 500
61+
}
62+
```
63+
64+
</TabItem>
65+
<TabItem value="Text">
66+
67+
```text
68+
defaultExpireAfterDays: 30
69+
defaultTrimMode : automatic
70+
majorVersionLimit : 500
71+
```
72+
73+
</TabItem>
74+
<TabItem value="CSV">
75+
76+
```csv
77+
defaultTrimMode,defaultExpireAfterDays,majorVersionLimit
78+
automatic,30,500
79+
```
80+
81+
</TabItem>
82+
<TabItem value="Markdown">
83+
84+
```md
85+
# spo site versionpolicy get --debug "false" --verbose "false" --siteUrl "https://contoso.sharepoint.com/"
86+
87+
Date: 8/23/2025
88+
89+
Property | Value
90+
---------|-------
91+
defaultTrimMode | automatic
92+
defaultExpireAfterDays | 30
93+
majorVersionLimit | 500
94+
```
95+
96+
</TabItem>
97+
</Tabs>

docs/src/config/sidebars.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3808,6 +3808,11 @@ const sidebars: SidebarsConfig = {
38083808
type: 'doc',
38093809
label: 'site sharingpermission set',
38103810
id: 'cmd/spo/site/site-sharingpermission-set'
3811+
},
3812+
{
3813+
type: 'doc',
3814+
label: 'site versionpolicy get',
3815+
id: 'cmd/spo/site/site-versionpolicy-get'
38113816
}
38123817
]
38133818
},

src/m365/spo/commands.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -286,6 +286,7 @@ export default {
286286
SITE_RENAME: `${prefix} site rename`,
287287
SITE_SET: `${prefix} site set`,
288288
SITE_SHARINGPERMISSION_SET: `${prefix} site sharingpermission set`,
289+
SITE_VERSIONPOLICY_GET: `${prefix} site versionpolicy get`,
289290
SITE_CHROME_SET: `${prefix} site chrome set`,
290291
SITE_UNARCHIVE: `${prefix} site unarchive`,
291292
SITEDESIGN_ADD: `${prefix} sitedesign add`,
Lines changed: 174 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,174 @@
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 { CommandInfo } from '../../../../cli/CommandInfo.js';
6+
import { Logger } from '../../../../cli/Logger.js';
7+
import { CommandError } from '../../../../Command.js';
8+
import request from '../../../../request.js';
9+
import { telemetry } from '../../../../telemetry.js';
10+
import { pid } from '../../../../utils/pid.js';
11+
import { session } from '../../../../utils/session.js';
12+
import { sinonUtil } from '../../../../utils/sinonUtil.js';
13+
import { z } from 'zod';
14+
import commands from '../../commands.js';
15+
import command from './site-versionpolicy-get.js';
16+
17+
describe(commands.SITE_VERSIONPOLICY_GET, () => {
18+
let log: any[];
19+
let logger: Logger;
20+
let loggerLogSpy: sinon.SinonSpy;
21+
let commandInfo: CommandInfo;
22+
let commandOptionsSchema: z.ZodTypeAny;
23+
const validSiteUrl = "https://contoso.sharepoint.com";
24+
25+
before(() => {
26+
sinon.stub(auth, 'restoreAuth').resolves();
27+
sinon.stub(telemetry, 'trackEvent').resolves();
28+
sinon.stub(pid, 'getProcessName').returns('');
29+
sinon.stub(session, 'getId').returns('');
30+
commandInfo = cli.getCommandInfo(command);
31+
commandOptionsSchema = commandInfo.command.getSchemaToParse()!;
32+
auth.connection.active = true;
33+
});
34+
35+
beforeEach(() => {
36+
log = [];
37+
logger = {
38+
log: async (msg: string) => {
39+
log.push(msg);
40+
},
41+
logRaw: async (msg: string) => {
42+
log.push(msg);
43+
},
44+
logToStderr: async (msg: string) => {
45+
log.push(msg);
46+
}
47+
};
48+
loggerLogSpy = sinon.spy(logger, 'log');
49+
});
50+
51+
afterEach(() => {
52+
sinonUtil.restore([
53+
request.get
54+
]);
55+
});
56+
57+
after(() => {
58+
sinon.restore();
59+
auth.connection.active = false;
60+
});
61+
62+
it('has correct name', () => {
63+
assert.strictEqual(command.name, commands.SITE_VERSIONPOLICY_GET);
64+
});
65+
66+
it('has a description', () => {
67+
assert.notStrictEqual(command.description, null);
68+
});
69+
70+
it('fails validation if site URL is not a valid URL', async () => {
71+
const actual = commandOptionsSchema.safeParse({ siteUrl: 'foo' });
72+
assert.strictEqual(actual.success, false);
73+
});
74+
75+
it('passes validation if valid site URL is specified', async () => {
76+
const actual = commandOptionsSchema.safeParse({ siteUrl: validSiteUrl });
77+
assert.strictEqual(actual.success, true);
78+
});
79+
80+
it('retrieves "age" version policy settings for the specified site', async () => {
81+
sinon.stub(request, 'get').callsFake(async (opts) => {
82+
if (opts.url === `${validSiteUrl}/_api/site/VersionPolicyForNewLibrariesTemplate?$expand=VersionPolicies`) {
83+
return {
84+
VersionPolicies: {
85+
DefaultTrimMode: 1,
86+
DefaultExpireAfterDays: 200
87+
},
88+
MajorVersionLimit: 100
89+
};
90+
}
91+
92+
throw 'Invalid request';
93+
});
94+
95+
await command.action(logger, { options: commandOptionsSchema.parse({ siteUrl: validSiteUrl }) });
96+
assert(loggerLogSpy.calledWith({
97+
defaultTrimMode: 'age',
98+
defaultExpireAfterDays: 200,
99+
majorVersionLimit: 100
100+
}));
101+
});
102+
103+
it('retrieves "automatic" version policy settings for the specified site', async () => {
104+
sinon.stub(request, 'get').callsFake(async (opts) => {
105+
if (opts.url === `${validSiteUrl}/_api/site/VersionPolicyForNewLibrariesTemplate?$expand=VersionPolicies`) {
106+
return {
107+
VersionPolicies: {
108+
DefaultTrimMode: 2,
109+
DefaultExpireAfterDays: 30
110+
},
111+
MajorVersionLimit: 500
112+
};
113+
}
114+
115+
throw 'Invalid request';
116+
});
117+
118+
await command.action(logger, { options: commandOptionsSchema.parse({ siteUrl: validSiteUrl }) });
119+
assert(loggerLogSpy.calledWith({
120+
defaultTrimMode: 'automatic',
121+
defaultExpireAfterDays: 30,
122+
majorVersionLimit: 500
123+
}));
124+
});
125+
126+
it('retrieves "number" version policy settings for the specified site', async () => {
127+
sinon.stub(request, 'get').callsFake(async (opts) => {
128+
if (opts.url === `${validSiteUrl}/_api/site/VersionPolicyForNewLibrariesTemplate?$expand=VersionPolicies`) {
129+
return {
130+
VersionPolicies: {
131+
DefaultTrimMode: 0,
132+
DefaultExpireAfterDays: 0
133+
},
134+
MajorVersionLimit: 300
135+
};
136+
}
137+
138+
throw 'Invalid request';
139+
});
140+
141+
await command.action(logger, { options: commandOptionsSchema.parse({ siteUrl: validSiteUrl, verbose: true }) });
142+
assert(loggerLogSpy.calledWith({
143+
defaultTrimMode: 'number',
144+
defaultExpireAfterDays: 0,
145+
majorVersionLimit: 300
146+
}));
147+
});
148+
149+
it('retrieves "inheritTenant" version policy settings for the specified site', async () => {
150+
sinon.stub(request, 'get').callsFake(async (opts) => {
151+
if (opts.url === `${validSiteUrl}/_api/site/VersionPolicyForNewLibrariesTemplate?$expand=VersionPolicies`) {
152+
return {
153+
MajorVersionLimit: -1
154+
};
155+
}
156+
157+
throw 'Invalid request';
158+
});
159+
160+
await command.action(logger, { options: commandOptionsSchema.parse({ siteUrl: validSiteUrl, verbose: true }) });
161+
assert(loggerLogSpy.calledWith({
162+
defaultTrimMode: 'inheritTenant',
163+
defaultExpireAfterDays: null,
164+
majorVersionLimit: -1
165+
}));
166+
});
167+
168+
it('correctly handles API OData error', async () => {
169+
sinon.stub(request, 'get').rejects(new Error('An error has occurred'));
170+
171+
await assert.rejects(command.action(logger, { options: commandOptionsSchema.parse({ siteUrl: validSiteUrl }) }),
172+
new CommandError('An error has occurred'));
173+
});
174+
});

0 commit comments

Comments
 (0)