From dfd5a3107f563705335d4f53c1cfc086b3b15ef4 Mon Sep 17 00:00:00 2001 From: Nanddeep Nachan Date: Thu, 2 May 2024 10:16:48 +0000 Subject: [PATCH 1/2] spo folder sharinglink get --- .../cmd/spo/folder/folder-sharinglink-get.mdx | 116 +++++++++++ docs/src/config/sidebars.ts | 5 + src/m365/spo/commands.ts | 1 + .../folder/folder-sharinglink-get.spec.ts | 193 ++++++++++++++++++ .../commands/folder/folder-sharinglink-get.ts | 125 ++++++++++++ src/utils/spo.ts | 92 ++++++++- 6 files changed, 531 insertions(+), 1 deletion(-) create mode 100644 docs/docs/cmd/spo/folder/folder-sharinglink-get.mdx create mode 100644 src/m365/spo/commands/folder/folder-sharinglink-get.spec.ts create mode 100644 src/m365/spo/commands/folder/folder-sharinglink-get.ts diff --git a/docs/docs/cmd/spo/folder/folder-sharinglink-get.mdx b/docs/docs/cmd/spo/folder/folder-sharinglink-get.mdx new file mode 100644 index 0000000000..7cebd14405 --- /dev/null +++ b/docs/docs/cmd/spo/folder/folder-sharinglink-get.mdx @@ -0,0 +1,116 @@ +import Global from '/docs/cmd/_global.mdx'; +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; + +# spo folder sharinglink get + +Gets details about a specific sharing link on a folder + +## Usage + +```sh +m365 spo folder sharinglink get [options] +``` + +## Options + +```md definition-list +`-u, --webUrl ` +: The URL of the site where the folder is located. + +`--folderUrl [folderUrl]` +: The server- or site-relative decoded URL of the folder. Specify either `folderUrl` or `folderId` but not both. + +`--folderId [folderId]` +: The UniqueId (GUID) of the folder. Specify either `folderUrl` or `folderId` but not both. + +`-i, --id ` +: The sharing link ID. +``` + + + +## Examples + +Gets a specific sharing link of a folder by id. + +```sh +m365 spo folder sharinglink get --webUrl https://contoso.sharepoint.com/sites/demo --id 45fa6aed-362f-48b1-b04e-6da85a526506 --folderId daebb04b-a773-4baa-b1d1-3625418e3234 +``` + +Gets a specific sharing link of a folder by url. + +```sh +m365 spo folder sharinglink get --webUrl https://contoso.sharepoint.com/sites/demo --id 45fa6aed-362f-48b1-b04e-6da85a526506 --folderUrl "/sites/demo/shared documents/folder" +``` + +## Response + + + + + ```json + { + "@odata.context": "https://graph.microsoft.com/v1.0/$metadata#drives('b!FeaYl4alXkWEhoSRP0ksSSOaj9osSfFPqj5bQNdluvlwfL79GNVISZZCf6nfB3vY')/items('01A5WCPNVP7VPKWR54VZCKFHBM773D5TE6')/permissions/$entity", + "@deprecated.GrantedToIdentities": "GrantedToIdentities has been deprecated. Refer to GrantedToIdentitiesV2", + "id": "d6f6a428-9857-471f-9635-edd68d5aa6c1", + "roles": [ + "write" + ], + "shareId": "u!aHR0cHM6Ly9uYWNoYW4zNjUuc2hhcmVwb2ludC5jb20vOmY6L3MvU1BEZW1vL0VxXzlYcXRIdks1RW9wd3NfX1kteko0QlNybFFNUy1qUTBFOWJsazhVLVNTdVE", + "hasPassword": false, + "grantedToIdentitiesV2": [], + "grantedToIdentities": [], + "link": { + "scope": "anonymous", + "type": "edit", + "webUrl": "https://contoso.sharepoint.com/:f:/s/demo/Eq_9XqtHvK5Eopws__Y-zJ4BSrlQMS-jQ0E9blk8U-SSuQ", + "preventsDownload": false + } + } + ``` + + + + + ```text + @deprecated.GrantedToIdentities: GrantedToIdentities has been deprecated. Refer to GrantedToIdentitiesV2 + @odata.context : https://graph.microsoft.com/v1.0/$metadata#drives('b!FeaYl4alXkWEhoSRP0ksSSOaj9osSfFPqj5bQNdluvlwfL79GNVISZZCf6nfB3vY')/items('01A5WCPNVP7VPKWR54VZCKFHBM773D5TE6')/permissions/$entity + grantedToIdentities : [] + grantedToIdentitiesV2 : [] + hasPassword : false + id : d6f6a428-9857-471f-9635-edd68d5aa6c1 + link : {"scope":"anonymous","type":"edit","webUrl":"https://contoso.sharepoint.com/:f:/s/demo/Eq_9XqtHvK5Eopws__Y-zJ4BSrlQMS-jQ0E9blk8U-SSuQ","preventsDownload":false} + roles : ["write"] + shareId : u!aHR0cHM6Ly9uYWNoYW4zNjUuc2hhcmVwb2ludC5jb20vOmY6L3MvU1BEZW1vL0VxXzlYcXRIdks1RW9wd3NfX1kteko0QlNybFFNUy1qUTBFOWJsazhVLVNTdVE + ``` + + + + + ```csv + @odata.context,@deprecated.GrantedToIdentities,id,shareId,hasPassword + https://graph.microsoft.com/v1.0/$metadata#drives('b!FeaYl4alXkWEhoSRP0ksSSOaj9osSfFPqj5bQNdluvlwfL79GNVISZZCf6nfB3vY')/items('01A5WCPNVP7VPKWR54VZCKFHBM773D5TE6')/permissions/$entity,GrantedToIdentities has been deprecated. Refer to GrantedToIdentitiesV2,d6f6a428-9857-471f-9635-edd68d5aa6c1,u!aHR0cHM6Ly9uYWNoYW4zNjUuc2hhcmVwb2ludC5jb20vOmY6L3MvU1BEZW1vL0VxXzlYcXRIdks1RW9wd3NfX1kteko0QlNybFFNUy1qUTBFOWJsazhVLVNTdVE, + ``` + + + + + ```md + # spo folder sharinglink get --webUrl "https://nachan365.sharepoint.com/sites/demo" --id "d6f6a428-9857-471f-9635-edd68d5aa6c1" --folderUrl "/sites/demo/shared documents/Test" + + Date: 5/2/2024 + + ## d6f6a428-9857-471f-9635-edd68d5aa6c1 + + Property | Value + ---------|------- + @odata.context | https://graph.microsoft.com/v1.0/$metadata#drives('b!FeaYl4alXkWEhoSRP0ksSSOaj9osSfFPqj5bQNdluvlwfL79GNVISZZCf6nfB3vY')/items('01A5WCPNVP7VPKWR54VZCKFHBM773D5TE6')/permissions/$entity + @deprecated.GrantedToIdentities | GrantedToIdentities has been deprecated. Refer to GrantedToIdentitiesV2 + id | d6f6a428-9857-471f-9635-edd68d5aa6c1 + shareId | u!aHR0cHM6Ly9uYWNoYW4zNjUuc2hhcmVwb2ludC5jb20vOmY6L3MvU1BEZW1vL0VxXzlYcXRIdks1RW9wd3NfX1kteko0QlNybFFNUy1qUTBFOWJsazhVLVNTdVE + hasPassword | false + ``` + + + diff --git a/docs/src/config/sidebars.ts b/docs/src/config/sidebars.ts index 50bd2573ae..2c973695e5 100644 --- a/docs/src/config/sidebars.ts +++ b/docs/src/config/sidebars.ts @@ -2506,6 +2506,11 @@ const sidebars: SidebarsConfig = { type: 'doc', label: 'folder roleinheritance reset', id: 'cmd/spo/folder/folder-roleinheritance-reset' + }, + { + type: 'doc', + label: 'folder sharinglink get', + id: 'cmd/spo/folder/folder-sharinglink-get' } ] }, diff --git a/src/m365/spo/commands.ts b/src/m365/spo/commands.ts index 76f699679b..8745e9dbe1 100644 --- a/src/m365/spo/commands.ts +++ b/src/m365/spo/commands.ts @@ -100,6 +100,7 @@ export default { FOLDER_ROLEASSIGNMENT_ADD: `${prefix} folder roleassignment add`, FOLDER_ROLEINHERITANCE_BREAK: `${prefix} folder roleinheritance break`, FOLDER_ROLEINHERITANCE_RESET: `${prefix} folder roleinheritance reset`, + FOLDER_SHARINGLINK_GET: `${prefix} folder sharinglink get`, GET: `${prefix} get`, GROUP_ADD: `${prefix} group add`, GROUP_GET: `${prefix} group get`, diff --git a/src/m365/spo/commands/folder/folder-sharinglink-get.spec.ts b/src/m365/spo/commands/folder/folder-sharinglink-get.spec.ts new file mode 100644 index 0000000000..0eabec63f3 --- /dev/null +++ b/src/m365/spo/commands/folder/folder-sharinglink-get.spec.ts @@ -0,0 +1,193 @@ +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 { CommandError } from '../../../../Command.js'; +import { telemetry } from '../../../../telemetry.js'; +import { Logger } from '../../../../cli/Logger.js'; +import request from '../../../../request.js'; +import { formatting } from '../../../../utils/formatting.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 './folder-sharinglink-get.js'; + +describe(commands.FOLDER_SHARINGLINK_GET, () => { + let log: any[]; + let logger: Logger; + let loggerLogSpy: sinon.SinonSpy; + let commandInfo: CommandInfo; + + const webUrl = 'https://contoso.sharepoint.com/sites/project-x'; + const folderUrl = '/sites/project-x/shared documents/folder1'; + const folderId = 'f09c4efe-b8c0-4e89-a166-03418661b89b'; + const id = 'd6f6a428-9857-471f-9635-edd68d5aa6c1'; + const siteId = '0f9b8f4f-0e8e-4630-bb0a-501442db9b64'; + const driveId = '013TMHP6UOOSLON57HT5GLKEU7R5UGWZVK'; + const itemId = 'b!T4-bD44OMEa7ClAUQtubZID9tc40pGJKpguycvELod_Gx-lo4ZQiRJ7vylonTufG'; + + const graphResponse = { + "@odata.context": "https://graph.microsoft.com/v1.0/$metadata#drives('b!T4-bD44OMEa7ClAUQtubZID9tc40pGJKpguycvELod_Gx-lo4ZQiRJ7vylonTufG')/items('013TMHP6UOOSLON57HT5GLKEU7R5UGWZVK')/permissions/$entity", + "@deprecated.GrantedToIdentities": "GrantedToIdentities has been deprecated. Refer to GrantedToIdentitiesV2", + "id": "d6f6a428-9857-471f-9635-edd68d5aa6c1", + "roles": [ + "write" + ], + "shareId": "u!aHR0cHM6Ly9uYWNoYW4zNjUuc2hhcmVwb2ludC5jb20vOmY6L3MvU1BEZW1vL0VxXzlYcXRIdks1RW9wd3NfX1kteko0QlNybFFNUy1qUTBFOWJsazhVLVNTdVE", + "hasPassword": false, + "grantedToIdentitiesV2": [], + "grantedToIdentities": [], + "link": { + "scope": "anonymous", + "type": "edit", + "webUrl": "https://contoso.sharepoint.com/:f:/s/project-x/Eq_9XqtHvK5Eopws__Y-zJ4BSrlQMS-jQ0E9blk8U-SSuQ", + "preventsDownload": false + } + }; + + const getDriveResponse: any = { + value: [ + { + "id": driveId, + "webUrl": `${webUrl}/Shared%20Documents` + } + ] + }; + + const defaultGetStub = (): sinon.SinonStub => { + return sinon.stub(request, 'get').callsFake(async opts => { + if (opts.url === `https://graph.microsoft.com/v1.0/sites/contoso.sharepoint.com:/sites/project-x?$select=id`) { + return { id: siteId }; + } + else if (opts.url === `https://graph.microsoft.com/v1.0/sites/${siteId}/drives?$select=webUrl,id`) { + return getDriveResponse; + } + else if (opts.url === `${webUrl}/_api/web/GetFolderById('${folderId}')?$select=ServerRelativeUrl`) { + return { ServerRelativeUrl: folderUrl }; + } + else if (opts.url === `${webUrl}/_api/web/GetFolderByServerRelativePath(decodedUrl='${formatting.encodeQueryParameter('/sites/project-x/shared documents/folder1')}')?$select=ServerRelativeUrl`) { + return { ServerRelativeUrl: '/sites/project-x/shared documents' }; + } + else if (opts.url === `${webUrl}/_api/web/GetFolderById('invalid')?$select=ServerRelativeUrl`) { + throw { error: { 'odata.error': { message: { value: 'File Not Found.' } } } }; + } + else if (opts.url === `https://graph.microsoft.com/v1.0/drives/${driveId}/root:/folder1?$select=id` || + opts.url === `https://graph.microsoft.com/v1.0/drives/${driveId}/root?$select=id`) { + return { id: itemId }; + } + else if (opts.url === `https://graph.microsoft.com/v1.0/drives/${driveId}/items/${itemId}/permissions/${id}`) { + return graphResponse; + } + + throw 'Invalid request'; + }); + }; + + 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); + } + }; + loggerLogSpy = sinon.spy(logger, 'log'); + }); + + afterEach(() => { + sinonUtil.restore([ + request.get + ]); + }); + + after(() => { + sinon.restore(); + auth.connection.active = false; + }); + + it('has correct name', () => { + assert.strictEqual(command.name, commands.FOLDER_SHARINGLINK_GET); + }); + + it('has a description', () => { + assert.notStrictEqual(command.description, null); + }); + + it('fails validation if the webUrl option is not a valid SharePoint site URL', async () => { + const actual = await command.validate({ options: { webUrl: 'foo', folderId: folderId, id: id } }, commandInfo); + assert.notStrictEqual(actual, true); + }); + + it('fails validation if the folderId option is not a valid GUID', async () => { + const actual = await command.validate({ options: { webUrl: webUrl, folderId: 'invalid', id: id } }, commandInfo); + assert.notStrictEqual(actual, true); + }); + + it('fails validation if the id option is not a valid GUID', async () => { + const actual = await command.validate({ options: { webUrl: webUrl, folderId: folderId, id: 'invalid' } }, commandInfo); + assert.notStrictEqual(actual, true); + }); + + it('passes validation if options are valid', async () => { + const actual = await command.validate({ options: { webUrl: webUrl, folderId: folderId, id: id } }, commandInfo); + assert.strictEqual(actual, true); + }); + + it('retrieves sharing link of folder specified by id', async () => { + defaultGetStub(); + + await command.action(logger, { options: { webUrl: webUrl, folderId: folderId, id: id } } as any); + assert(loggerLogSpy.calledWith(graphResponse)); + }); + + it('retrieves sharing link of folder specified by url', async () => { + defaultGetStub(); + + await command.action(logger, { options: { webUrl: webUrl, folderUrl: folderUrl, id: id } } as any); + assert(loggerLogSpy.calledWith(graphResponse)); + }); + + it('throws error when folder not found by id', async () => { + defaultGetStub(); + + await assert.rejects(command.action(logger, { options: { webUrl: webUrl, folderId: 'invalid', id: id, verbose: true } } as any), + new CommandError(`File Not Found.`)); + }); + + it('throws error when drive not found by url', async () => { + sinon.stub(request, 'get').callsFake(async opts => { + if (opts.url === `${webUrl}/_api/web/GetFolderByServerRelativePath(decodedUrl='${formatting.encodeQueryParameter(folderUrl)}')?$select=ServerRelativeUrl`) { + return { ServerRelativeUrl: folderUrl }; + } + else if (opts.url === `https://graph.microsoft.com/v1.0/sites/contoso.sharepoint.com:/sites/project-x?$select=id`) { + return { id: siteId }; + } + else if (opts.url === `https://graph.microsoft.com/v1.0/sites/${siteId}/drives?$select=webUrl,id`) { + return { + value: [] + }; + } + + throw 'Invalid request'; + }); + + await assert.rejects(command.action(logger, { options: { webUrl: webUrl, folderUrl: folderUrl, id: id, verbose: true } } as any), + new CommandError(`Drive 'https://contoso.sharepoint.com/sites/project-x/shared%20documents/folder1' not found`)); + }); +}); \ No newline at end of file diff --git a/src/m365/spo/commands/folder/folder-sharinglink-get.ts b/src/m365/spo/commands/folder/folder-sharinglink-get.ts new file mode 100644 index 0000000000..db094b1935 --- /dev/null +++ b/src/m365/spo/commands/folder/folder-sharinglink-get.ts @@ -0,0 +1,125 @@ +import { Permission } from '@microsoft/microsoft-graph-types'; +import { Logger } from '../../../../cli/Logger.js'; +import GlobalOptions from '../../../../GlobalOptions.js'; +import request, { CliRequestOptions } from '../../../../request.js'; +import { spo } from '../../../../utils/spo.js'; +import { urlUtil } from '../../../../utils/urlUtil.js'; +import { validation } from '../../../../utils/validation.js'; +import SpoCommand from '../../../base/SpoCommand.js'; +import { Drive } from '@microsoft/microsoft-graph-types'; +import commands from '../../commands.js'; + +interface CommandArgs { + options: Options; +} + +interface Options extends GlobalOptions { + webUrl: string; + folderUrl?: string; + folderId?: string; + id: string; +} + +class SpoFolderSharingLinkGetCommand extends SpoCommand { + public get name(): string { + return commands.FOLDER_SHARINGLINK_GET; + } + + public get description(): string { + return 'Gets details about a specific sharing link on a folder'; + } + + constructor() { + super(); + + this.#initTelemetry(); + this.#initOptions(); + this.#initOptionSets(); + this.#initValidators(); + this.#initTypes(); + } + + #initTelemetry(): void { + this.telemetry.push((args: CommandArgs) => { + Object.assign(this.telemetryProperties, { + webUrl: typeof args.options.webUrl !== 'undefined', + folderUrl: typeof args.options.folderUrl !== 'undefined', + folderId: typeof args.options.folderId !== 'undefined' + }); + }); + } + + #initOptions(): void { + this.options.unshift( + { option: '-u, --webUrl ' }, + { option: '--folderUrl [folderUrl]' }, + { option: '--folderId [folderId]' }, + { option: '-i, --id ' } + ); + } + + #initOptionSets(): void { + this.optionSets.push( + { options: ['folderUrl', 'folderId'] } + ); + } + + #initValidators(): void { + this.validators.push( + async (args: CommandArgs) => { + const isValidSharePointUrl: boolean | string = validation.isValidSharePointUrl(args.options.webUrl); + if (isValidSharePointUrl !== true) { + return isValidSharePointUrl; + } + + if (args.options.folderId && !validation.isValidGuid(args.options.folderId)) { + return `${args.options.folderId} is not a valid GUID`; + } + + if (!validation.isValidGuid(args.options.id)) { + return `${args.options.id} is not a valid GUID`; + } + + return true; + } + ); + } + + #initTypes(): void { + this.types.string.push('webUrl', 'folderUrl', 'folderId', 'id'); + } + + public async commandAction(logger: Logger, args: CommandArgs): Promise { + if (this.verbose) { + await logger.logToStderr(`Retrieving sharing link on folder ${args.options.folderId || args.options.folderUrl}...`); + } + + try { + const relFolderUrl: string = await spo.getFolderServerRelativeUrl(args.options.webUrl, args.options.folderUrl, args.options.folderId); + const absoluteFolderUrl: string = urlUtil.getAbsoluteUrl(args.options.webUrl, relFolderUrl); + const folderUrl: URL = new URL(absoluteFolderUrl); + + const siteId: string = await spo.getSiteId(args.options.webUrl); + const drive: Drive = await spo.getDrive(siteId, folderUrl); + const itemId: string = await spo.getDriveItemId(drive, folderUrl); + + const requestUrl = `https://graph.microsoft.com/v1.0/drives/${drive.id}/items/${itemId}/permissions/${args.options.id}`; + + const requestOptions: CliRequestOptions = { + url: requestUrl, + headers: { + 'accept': 'application/json;odata.metadata=none' + }, + responseType: 'json' + }; + + const permission = await request.get(requestOptions); + await logger.log(permission); + } + catch (err: any) { + this.handleRejectedODataJsonPromise(err); + } + } +} + +export default new SpoFolderSharingLinkGetCommand(); \ No newline at end of file diff --git a/src/utils/spo.ts b/src/utils/spo.ts index 6330aa428a..c660c9d9e8 100644 --- a/src/utils/spo.ts +++ b/src/utils/spo.ts @@ -18,7 +18,7 @@ import { SiteProperties } from '../m365/spo/commands/site/SiteProperties.js'; import { entraGroup } from './entraGroup.js'; import { SharingCapabilities } from '../m365/spo/commands/site/SharingCapabilities.js'; import { WebProperties } from '../m365/spo/commands/web/WebProperties.js'; -import { Site } from '@microsoft/microsoft-graph-types'; +import { Site, Drive, DriveItem } from '@microsoft/microsoft-graph-types'; import { ListItemInstance } from '../m365/spo/commands/listitem/ListItemInstance.js'; import { ListItemFieldValueResult } from '../m365/spo/commands/listitem/ListItemFieldValueResult.js'; import { FileProperties } from '../m365/spo/commands/file/FileProperties.js'; @@ -1743,6 +1743,96 @@ export const spo = { return site.id as string; }, + /** + * Retrieves the server-relative URL of a folder. + * @param webUrl Web URL + * @param folderUrl Folder URL + * @param folderId Folder ID + * @returns The server-relative URL of the folder + */ + async getFolderServerRelativeUrl(webUrl: string, folderUrl: string | undefined, folderId: string | undefined): Promise { + let requestUrl: string = `${webUrl}/_api/web/`; + + if (folderUrl) { + const folderServerRelativeUrl: string = urlUtil.getServerRelativePath(webUrl, folderUrl); + requestUrl += `GetFolderByServerRelativePath(decodedUrl='${formatting.encodeQueryParameter(folderServerRelativeUrl)}')`; + } + else { + requestUrl += `GetFolderById('${folderId}')`; + } + + requestUrl += '?$select=ServerRelativeUrl'; + + const requestOptions: CliRequestOptions = { + url: requestUrl, + headers: { + accept: 'application/json;odata=nometadata' + }, + responseType: 'json' + }; + + const res = await request.get<{ ServerRelativeUrl: string }>(requestOptions); + return res.ServerRelativeUrl; + }, + + /** + * Retrieves the Drive associated with the specified site and folder URL. + * @param siteId Site ID + * @param folderUrl Folder URL + * @returns The Drive associated with the folder URL. + */ + async getDrive(siteId: string, folderUrl: URL): Promise { + const requestOptions: CliRequestOptions = { + url: `https://graph.microsoft.com/v1.0/sites/${siteId}/drives?$select=webUrl,id`, + headers: { + accept: 'application/json;odata.metadata=none' + }, + responseType: 'json' + }; + + const drives = await request.get<{ value: Drive[] }>(requestOptions); + + const lowerCaseFolderUrl: string = folderUrl.href.toLowerCase(); + + const drive: Drive | undefined = drives.value + .sort((a, b) => (b.webUrl as string).localeCompare(a.webUrl as string)) + .find((d: Drive) => { + const driveUrl: string = (d.webUrl as string).toLowerCase(); + + return lowerCaseFolderUrl.startsWith(driveUrl) && + (driveUrl.length === lowerCaseFolderUrl.length || + lowerCaseFolderUrl[driveUrl.length] === '/'); + }); + + if (!drive) { + throw `Drive '${folderUrl.href}' not found`; + } + + return drive; + }, + + /** + * Retrieves the ID of a drive item (file, folder, etc.) associated with the given drive and item URL. + * @param drive The Drive object containing the item + * @param itemUrl Item URL + * @returns Drive item ID + */ + async getDriveItemId(drive: Drive, itemUrl: URL): Promise { + const relativeItemUrl: string = itemUrl.href.replace(new RegExp(`${drive.webUrl}`, 'i'), '').replace(/\/+$/, ''); + + const requestOptions: CliRequestOptions = { + url: `https://graph.microsoft.com/v1.0/drives/${drive.id}/root${relativeItemUrl ? `:${relativeItemUrl}` : ''}?$select=id`, + headers: { + accept: 'application/json;odata.metadata=none' + }, + responseType: 'json' + }; + + const driveItem = await request.get(requestOptions); + + return driveItem?.id as string; + }, + /** * Retrieves the ObjectIdentity from a SharePoint site * @param webUrl web url From 915a741776fcd4f52a4d10da186909e0b14804c4 Mon Sep 17 00:00:00 2001 From: Nanddeep Nachan Date: Wed, 19 Jun 2024 17:47:18 +0000 Subject: [PATCH 2/2] Added test cases --- src/utils/spo.spec.ts | 75 ++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 74 insertions(+), 1 deletion(-) diff --git a/src/utils/spo.spec.ts b/src/utils/spo.spec.ts index 42028dac1b..968ffeb32e 100644 --- a/src/utils/spo.spec.ts +++ b/src/utils/spo.spec.ts @@ -10,6 +10,7 @@ import { sinonUtil } from '../utils/sinonUtil.js'; import { FormDigestInfo, spo } from '../utils/spo.js'; import { entraGroup } from './entraGroup.js'; import { formatting } from './formatting.js'; +import { Drive } from '@microsoft/microsoft-graph-types'; const stubPostResponses: any = ( folderAddResp: any = null @@ -49,6 +50,16 @@ describe('utils/spo', () => { let log: string[]; let loggerLogSpy: sinon.SinonSpy; + const siteId = '0f9b8f4f-0e8e-4630-bb0a-501442db9b64'; + const driveId = '013TMHP6UOOSLON57HT5GLKEU7R5UGWZVK'; + const itemId = 'b!T4-bD44OMEa7ClAUQtubZID9tc40pGJKpguycvELod_Gx-lo4ZQiRJ7vylonTufG'; + const webUrl = 'https://contoso.sharepoint.com/sites/sales'; + const folderUrl: URL = new URL('https://contoso.sharepoint.com/sites/sales/shared%20documents/'); + const drive: Drive = { + id: driveId, + webUrl: `${webUrl}/Shared%20Documents` + }; + before(() => { auth.connection.active = true; }); @@ -989,7 +1000,6 @@ describe('utils/spo', () => { //#region Navigation menu state responses const topNavigationResponse = { 'AudienceIds': [], 'FriendlyUrlPrefix': '', 'IsAudienceTargetEnabledForGlobalNav': false, 'Nodes': [{ 'AudienceIds': [], 'CurrentLCID': 1033, 'CustomProperties': [], 'FriendlyUrlSegment': '', 'IsDeleted': false, 'IsHidden': false, 'IsTitleForExistingLanguage': false, 'Key': '2039', 'Nodes': [{ 'AudienceIds': [], 'CurrentLCID': 1033, 'CustomProperties': [], 'FriendlyUrlSegment': '', 'IsDeleted': false, 'IsHidden': false, 'IsTitleForExistingLanguage': false, 'Key': '2041', 'Nodes': [], 'NodeType': 0, 'OpenInNewWindow': true, 'SimpleUrl': '/sites/PnPCoreSDKTestGroup', 'Title': 'Sub level 1', 'Translations': [] }], 'NodeType': 0, 'OpenInNewWindow': null, 'SimpleUrl': '/sites/PnPCoreSDKTestGroup', 'Title': 'Site A', 'Translations': [] }, { 'AudienceIds': [], 'CurrentLCID': 1033, 'CustomProperties': [], 'FriendlyUrlSegment': '', 'IsDeleted': false, 'IsHidden': false, 'IsTitleForExistingLanguage': false, 'Key': '2040', 'Nodes': [], 'NodeType': 0, 'OpenInNewWindow': true, 'SimpleUrl': '/sites/PnPCoreSDKTestGroup', 'Title': 'Site B', 'Translations': [] }, { 'AudienceIds': [], 'CurrentLCID': 1033, 'CustomProperties': [], 'FriendlyUrlSegment': '', 'IsDeleted': false, 'IsHidden': false, 'IsTitleForExistingLanguage': false, 'Key': '2001', 'Nodes': [], 'NodeType': 0, 'OpenInNewWindow': true, 'SimpleUrl': '/sites/team-a/sitepages/about.aspx', 'Title': 'About', 'Translations': [] }], 'SimpleUrl': '', 'SPSitePrefix': '/sites/SharePointDemoSite', 'SPWebPrefix': '/sites/SharePointDemoSite', 'StartingNodeKey': '1025', 'StartingNodeTitle': 'Quick launch', 'Version': '2023-03-09T18:33:53.5468097Z' }; const quickLaunchResponse = { 'AudienceIds': [], 'FriendlyUrlPrefix': '', 'IsAudienceTargetEnabledForGlobalNav': false, 'Nodes': [{ 'AudienceIds': [], 'CurrentLCID': 1033, 'CustomProperties': [], 'FriendlyUrlSegment': '', 'IsDeleted': false, 'IsHidden': false, 'IsTitleForExistingLanguage': false, 'Key': '2003', 'Nodes': [{ 'AudienceIds': [], 'CurrentLCID': 1033, 'CustomProperties': [], 'FriendlyUrlSegment': '', 'IsDeleted': false, 'IsHidden': false, 'IsTitleForExistingLanguage': false, 'Key': '2006', 'Nodes': [], 'NodeType': 0, 'OpenInNewWindow': null, 'SimpleUrl': '/sites/SharePointDemoSite#/', 'Title': 'Sub Item', 'Translations': [] }], 'NodeType': 0, 'OpenInNewWindow': true, 'SimpleUrl': 'http://google.be', 'Title': 'Site A', 'Translations': [] }, { 'AudienceIds': [], 'CurrentLCID': 1033, 'CustomProperties': [], 'FriendlyUrlSegment': '', 'IsDeleted': false, 'IsHidden': false, 'IsTitleForExistingLanguage': false, 'Key': '2018', 'Nodes': [], 'NodeType': 0, 'OpenInNewWindow': null, 'SimpleUrl': 'https://google.be', 'Title': 'Site B', 'Translations': [] }], 'SimpleUrl': '', 'SPSitePrefix': '/sites/SharePointDemoSite', 'SPWebPrefix': '/sites/SharePointDemoSite', 'StartingNodeKey': '1002', 'StartingNodeTitle': 'SharePoint Top Navigation Bar', 'Version': '2023-03-09T18:34:53.650545Z' }; - const webUrl = 'https://contoso.sharepoint.com/sites/sales'; //#endregion it(`retrieves the quick launch navigation response`, async () => { @@ -2085,6 +2095,69 @@ describe('utils/spo', () => { assert.strictEqual(id, 'contoso.sharepoint.com,ea49a393-e3e6-4760-a1b2-e96539e15372,66e2861c-96d9-4418-a75c-0ed1bca68b42'); }); + it('returns the folder server relative URL by URL', async () => { + sinon.stub(request, 'get').callsFake(async (opts) => { + if (opts.url === `${webUrl}/_api/web/GetFolderByServerRelativePath(decodedUrl='${formatting.encodeQueryParameter('/sites/sales/shared documents/folder1')}')?$select=ServerRelativeUrl` + ) { + return { ServerRelativeUrl: '/sites/sales/shared documents/folder1' }; + } + + throw 'Invalid request'; + }); + + const url = await spo.getFolderServerRelativeUrl(webUrl, '/sites/sales/shared documents/folder1', undefined); + + assert.strictEqual(url, '/sites/sales/shared documents/folder1'); + }); + + it('returns the folder server relative URL by id', async () => { + sinon.stub(request, 'get').callsFake(async (opts) => { + if (opts.url === `${webUrl}/_api/web/GetFolderById('f09c4efe-b8c0-4e89-a166-03418661b89b')?$select=ServerRelativeUrl`) { + return { ServerRelativeUrl: '/sites/sales/shared documents/folder1' }; + } + + throw 'Invalid request'; + }); + + const url = await spo.getFolderServerRelativeUrl(webUrl, undefined, 'f09c4efe-b8c0-4e89-a166-03418661b89b'); + + assert.strictEqual(url, '/sites/sales/shared documents/folder1'); + }); + + it('correctly gets drive using getDrive', async () => { + sinon.stub(request, 'get').callsFake(async opts => { + if (opts.url === `https://graph.microsoft.com/v1.0/sites/${siteId}/drives?$select=webUrl,id`) { + return { + value: [ + { + "id": "013TMHP6UOOSLON57HT5GLKEU7R5UGWZVK", + "webUrl": `${webUrl}/Shared%20Documents` + } + ] + }; + } + + return 'Invalid Request'; + }); + + const actual = await spo.getDrive(siteId, folderUrl); + assert.deepStrictEqual(actual, drive); + }); + + it('correctly gets drive item ID using getDriveItemId', async () => { + sinon.stub(request, 'get').callsFake(async opts => { + const relativeItemUrl = folderUrl.href.replace(new RegExp(`${drive.webUrl}`, 'i'), '').replace(/\/+$/, ''); + if (opts.url === `https://graph.microsoft.com/v1.0/drives/${driveId}/root${relativeItemUrl ? `:${relativeItemUrl}` : ''}?$select=id`) { + return { id: itemId }; + } + + return 'Invalid Request'; + }); + + const actual = await spo.getDriveItemId(drive, folderUrl); + assert.strictEqual(actual, itemId); + }); + it(`get the file properties with the server relative url`, async () => { const fileResponse = { ListItemAllFields: {