Skip to content

Commit

Permalink
Adds command spp model remove. Closes pnp#6118
Browse files Browse the repository at this point in the history
  • Loading branch information
mkm17 committed Oct 13, 2024
1 parent da65889 commit 9a84d0e
Show file tree
Hide file tree
Showing 7 changed files with 520 additions and 1 deletion.
53 changes: 53 additions & 0 deletions docs/docs/cmd/spp/model/model-remove.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import Global from '/docs/cmd/_global.mdx';
import Tabs from '@theme/Tabs';
import TabItem from '@theme/TabItem';

# spp model remove

Deletes a document understanding model

## Usage

```sh
m365 spp model remove [options]
```

## Options

```md definition-list
`-u, --siteUrl <siteUrl>`
: The URL of the content center site.

`-i, --id [id]`
: The unique ID of the model to delete. Specify either `id` or `title` but not both.

`-t, --title [title]`
: The display name (case-sensitive) of the model to remove. Specify either `id` or `title` but not both.

`-f --force`
: Don't prompt for confirming removing the model.
```

<Global />

## Remarks

Note that this model will be removed from all libraries before it can be deleted.

## Examples

Delete a SharePoint Premium document understanding model using the model’s UniqueId.

```sh
m365 spp model remove --siteUrl "https://contoso.sharepoint.com/sites/ContentCenter" --id "7645e69d-21fb-4a24-a17a-9bdfa7cb63dc"
```

Delete a SharePoint Premium document understanding model using the model’s title.

```sh
m365 spp model remove --siteUrl "https://contoso.sharepoint.com/sites/ContentCenter" --title "climicrosoft365Model.classifier"
```

## Response

The command won't return a response on success.
9 changes: 9 additions & 0 deletions docs/src/config/sidebars.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3952,6 +3952,15 @@ const sidebars: SidebarsConfig = {
id: 'cmd/spp/contentcenter/contentcenter-list'
}
]
},
{
model: [
{
type: 'doc',
label: 'model remove',
id: 'cmd/spp/model/model-remove'
}
]
}
]
},
Expand Down
3 changes: 2 additions & 1 deletion src/m365/spp/commands.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
const prefix: string = 'spp';

export default {
CONTENTCENTER_LIST: `${prefix} contentcenter list`
CONTENTCENTER_LIST: `${prefix} contentcenter list`,
MODEL_REMOVE: `${prefix} model remove`
};
248 changes: 248 additions & 0 deletions src/m365/spp/commands/model/model-remove.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,248 @@
import assert from 'assert';
import sinon from 'sinon';
import auth from '../../../../Auth.js';
import { cli } from '../../../../cli/cli.js';
import { CommandInfo } from '../../../../cli/CommandInfo.js';
import { Logger } from '../../../../cli/Logger.js';
import { CommandError } from '../../../../Command.js';
import request from '../../../../request.js';
import { telemetry } from '../../../../telemetry.js';
import { pid } from '../../../../utils/pid.js';
import { session } from '../../../../utils/session.js';
import { sinonUtil } from '../../../../utils/sinonUtil.js';
import commands from '../../commands.js';
import command from './model-remove.js';

describe(commands.MODEL_REMOVE, () => {
let log: string[];
let logger: Logger;
let commandInfo: CommandInfo;

before(() => {
sinon.stub(auth, 'restoreAuth').resolves();
sinon.stub(telemetry, 'trackEvent').returns();
sinon.stub(pid, 'getProcessName').returns('');
sinon.stub(session, 'getId').returns('');
auth.connection.active = true;
commandInfo = cli.getCommandInfo(command);
});

beforeEach(() => {
log = [];
logger = {
log: async (msg: string) => {
log.push(msg);
},
logRaw: async (msg: string) => {
log.push(msg);
},
logToStderr: async (msg: string) => {
log.push(msg);
}
};
});

afterEach(() => {
sinonUtil.restore([
request.get,
request.delete,
cli.promptForConfirmation
]);
});

after(() => {
sinon.restore();
auth.connection.active = false;
});

it('has correct name', () => {
assert.strictEqual(command.name, commands.MODEL_REMOVE);
});

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

it('passes validation when required parameters are valid with id', async () => {
const actual = await command.validate({ options: { siteUrl: 'https://contoso.sharepoint.com/sites/sales', id: '9b1b1e42-794b-4c71-93ac-5ed92488b67f' } }, commandInfo);
assert.strictEqual(actual, true);
});

it('passes validation when required parameters are valid with title', async () => {
const actual = await command.validate({ options: { siteUrl: 'https://contoso.sharepoint.com/sites/sales', title: 'ModelName' } }, commandInfo);
assert.strictEqual(actual, true);
});

it('passes validation when required parameters are valid with id and force', async () => {
const actual = await command.validate({ options: { siteUrl: 'https://contoso.sharepoint.com/sites/sales', id: '9b1b1e42-794b-4c71-93ac-5ed92488b67f', force: true } }, commandInfo);
assert.strictEqual(actual, true);
});

it('fails validation when siteUrl is not valid', async () => {
const actual = await command.validate({ options: { siteUrl: 'invalidUrl', id: '9b1b1e42-794b-4c71-93ac-5ed92488b67f' } }, commandInfo);
assert.notStrictEqual(actual, true);
});

it('fails validation when id is not valid', async () => {
const actual = await command.validate({ options: { siteUrl: 'https://contoso.sharepoint.com/sites/sales', id: 'foo' } }, commandInfo);
assert.notStrictEqual(actual, true);
});

it('correctly handles site is not Content Site', async () => {
sinon.stub(request, 'get').callsFake(async (opts) => {
if (opts.url === `https://contoso.sharepoint.com/sites/portal/_api/web?$select=WebTemplateConfiguration`) {
return {
WebTemplateConfiguration: 'SITEPAGEPUBLISHING#0'
};
}

throw 'Invalid request';
});

await assert.rejects(command.action(logger, { options: { verbose: true, siteUrl: 'https://contoso.sharepoint.com/sites/portal', id: '9b1b1e42-794b-4c71-93ac-5ed92488b67f', force: true } }),
new CommandError('https://contoso.sharepoint.com/sites/portal is not a content site.'));
});


it('correctly handles an access denied error', async () => {
sinon.stub(request, 'get').callsFake(async (opts) => {
if (opts.url === `https://contoso.sharepoint.com/sites/portal/_api/web?$select=WebTemplateConfiguration`) {
throw {
error: {
"odata.error": {
message: {
lang: "en-US",
value: "Attempted to perform an unauthorized operation."
}
}
}
};
}

throw 'Invalid request';
});

await assert.rejects(command.action(logger, { options: { verbose: true, siteUrl: 'https://contoso.sharepoint.com/sites/portal', id: '9b1b1e42-794b-4c71-93ac-5ed92488b67f', force: true } }),
new CommandError('Attempted to perform an unauthorized operation.'));
});


it('deletes model by id', async () => {
const confirmationStub = sinon.stub(cli, 'promptForConfirmation').resolves(true);
sinon.stub(request, 'get').callsFake(async (opts) => {
if (opts.url === `https://contoso.sharepoint.com/sites/portal/_api/web?$select=WebTemplateConfiguration`) {
return {
WebTemplateConfiguration: 'CONTENTCTR#0'
};
}
throw 'Invalid request';
});

const stubDelete = sinon.stub(request, 'delete').callsFake(async (opts) => {
if (opts.url === `https://contoso.sharepoint.com/sites/portal/_api/machinelearning/models/getbyuniqueid('9b1b1e42-794b-4c71-93ac-5ed92488b67f')`) {
return;
}

throw 'Invalid request';
});

await command.action(logger, { options: { siteUrl: 'https://contoso.sharepoint.com/sites/portal', id: '9b1b1e42-794b-4c71-93ac-5ed92488b67f' } });
assert.strictEqual(stubDelete.lastCall.args[0].url, `https://contoso.sharepoint.com/sites/portal/_api/machinelearning/models/getbyuniqueid('9b1b1e42-794b-4c71-93ac-5ed92488b67f')`);
assert(confirmationStub.calledOnce);
});

it('does not delete model when confirmation is not accepted', async () => {
const confirmationStub = sinon.stub(cli, 'promptForConfirmation').resolves(false);
sinon.stub(request, 'get').callsFake(async (opts) => {
if (opts.url === `https://contoso.sharepoint.com/sites/portal/_api/web?$select=WebTemplateConfiguration`) {
return {
WebTemplateConfiguration: 'CONTENTCTR#0'
};
}
throw 'Invalid request';
});

const stubDelete = sinon.stub(request, 'delete').callsFake(async (opts) => {
if (opts.url === `https://contoso.sharepoint.com/sites/portal/_api/machinelearning/models/getbyuniqueid('9b1b1e42-794b-4c71-93ac-5ed92488b67f')`) {
return;
}

throw 'Invalid request';
});

await command.action(logger, { options: { siteUrl: 'https://contoso.sharepoint.com/sites/portal', id: '9b1b1e42-794b-4c71-93ac-5ed92488b67f' } });
assert(stubDelete.notCalled);
assert(confirmationStub.calledOnce);
});

it('deletes model by id with force', async () => {
sinon.stub(request, 'get').callsFake(async (opts) => {
if (opts.url === `https://contoso.sharepoint.com/sites/portal/_api/web?$select=WebTemplateConfiguration`) {
return {
WebTemplateConfiguration: 'CONTENTCTR#0'
};
}
throw 'Invalid request';
});

const stubDelete = sinon.stub(request, 'delete').callsFake(async (opts) => {
if (opts.url === `https://contoso.sharepoint.com/sites/portal/_api/machinelearning/models/getbyuniqueid('164720c8-35ee-4157-ba26-db6726264f9d')`) {
return;
}

throw 'Invalid request';
});

await command.action(logger, { options: { siteUrl: 'https://contoso.sharepoint.com/sites/portal', id: '164720c8-35ee-4157-ba26-db6726264f9d', force: true } });
assert.strictEqual(stubDelete.lastCall.args[0].url, `https://contoso.sharepoint.com/sites/portal/_api/machinelearning/models/getbyuniqueid('164720c8-35ee-4157-ba26-db6726264f9d')`);
});

it('deletes model when the the site URL has trailing slash', async () => {
const confirmationStub = sinon.stub(cli, 'promptForConfirmation').resolves(true);
sinon.stub(request, 'get').callsFake(async (opts) => {
if (opts.url === `https://contoso.sharepoint.com/sites/portal/_api/web?$select=WebTemplateConfiguration`) {
return {
WebTemplateConfiguration: 'CONTENTCTR#0'
};
}
throw 'Invalid request';
});

const stubDelete = sinon.stub(request, 'delete').callsFake(async (opts) => {
if (opts.url === `https://contoso.sharepoint.com/sites/portal/_api/machinelearning/models/getbyuniqueid('9b1b1e42-794b-4c71-93ac-5ed92488b67f')`) {
return;
}

throw 'Invalid request';
});

await command.action(logger, { options: { siteUrl: 'https://contoso.sharepoint.com/sites/portal/', id: '9b1b1e42-794b-4c71-93ac-5ed92488b67f' } });
assert.strictEqual(stubDelete.lastCall.args[0].url, `https://contoso.sharepoint.com/sites/portal/_api/machinelearning/models/getbyuniqueid('9b1b1e42-794b-4c71-93ac-5ed92488b67f')`);
assert(confirmationStub.calledOnce);
});

it('deletes model by title', async () => {
const confirmationStub = sinon.stub(cli, 'promptForConfirmation').resolves(true);
sinon.stub(request, 'get').callsFake(async (opts) => {
if (opts.url === `https://contoso.sharepoint.com/sites/portal/_api/web?$select=WebTemplateConfiguration`) {
return {
WebTemplateConfiguration: 'CONTENTCTR#0'
};
}

throw 'Invalid request';
});

const stubDelete = sinon.stub(request, 'delete').callsFake(async (opts) => {
if (opts.url === `https://contoso.sharepoint.com/sites/portal/_api/machinelearning/models/getbytitle('ModelName')`) {
return;
}

throw 'Invalid request';
});

await command.action(logger, { options: { siteUrl: 'https://contoso.sharepoint.com/sites/portal', title: 'ModelName' } });
assert.strictEqual(stubDelete.lastCall.args[0].url, `https://contoso.sharepoint.com/sites/portal/_api/machinelearning/models/getbytitle('ModelName')`);
assert(confirmationStub.calledOnce);
});
});
Loading

0 comments on commit 9a84d0e

Please sign in to comment.