Skip to content

Commit aedfd05

Browse files
committed
New command: spo site versionpolicy get. Closes #6750
1 parent 24af61c commit aedfd05

File tree

6 files changed

+388
-0
lines changed

6 files changed

+388
-0
lines changed

.devproxy/api-specs/sharepoint.yaml

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,30 @@ paths:
7575
responses:
7676
200:
7777
description: OK
78+
/_api/site/VersionPolicyForNewLibrariesTemplate:
79+
get:
80+
parameters:
81+
- name: $expand
82+
in: query
83+
required: false
84+
description: Expand related entities
85+
schema:
86+
type: string
87+
example: VersionPolicies
88+
security:
89+
- delegated:
90+
- AllSites.Read
91+
- AllSites.Write
92+
- AllSites.Manage
93+
- AllSites.FullControl
94+
- application:
95+
- Sites.Read.All
96+
- Sites.ReadWrite.All
97+
- Sites.Manage.All
98+
- Sites.FullControl.All
99+
responses:
100+
200:
101+
description: OK
78102
/_api/web:
79103
get:
80104
security:
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
@@ -3824,6 +3824,11 @@ const sidebars: SidebarsConfig = {
38243824
type: 'doc',
38253825
label: 'site sharingpermission set',
38263826
id: 'cmd/spo/site/site-sharingpermission-set'
3827+
},
3828+
{
3829+
type: 'doc',
3830+
label: 'site versionpolicy get',
3831+
id: 'cmd/spo/site/site-versionpolicy-get'
38273832
}
38283833
]
38293834
},

src/m365/spo/commands.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -283,6 +283,7 @@ export default {
283283
SITE_REMOVE: `${prefix} site remove`,
284284
SITE_SET: `${prefix} site set`,
285285
SITE_SHARINGPERMISSION_SET: `${prefix} site sharingpermission set`,
286+
SITE_VERSIONPOLICY_GET: `${prefix} site versionpolicy get`,
286287
SITE_CHROME_SET: `${prefix} site chrome set`,
287288
SITEDESIGN_ADD: `${prefix} sitedesign add`,
288289
SITEDESIGN_APPLY: `${prefix} sitedesign apply`,
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 = await command.validate({ options: { siteUrl: validSiteUrl } }, commandInfo);
77+
assert.strictEqual(actual, 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: { 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: { 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: { 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: { 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: { siteUrl: validSiteUrl } }),
172+
new CommandError('An error has occurred'));
173+
});
174+
});

0 commit comments

Comments
 (0)