diff --git a/packages/nodes-base/nodes/Microsoft/SharePoint/MicrosoftSharePointTrigger.node.ts b/packages/nodes-base/nodes/Microsoft/SharePoint/MicrosoftSharePointTrigger.node.ts index 61718b3930d..3b6e4d1fd13 100644 --- a/packages/nodes-base/nodes/Microsoft/SharePoint/MicrosoftSharePointTrigger.node.ts +++ b/packages/nodes-base/nodes/Microsoft/SharePoint/MicrosoftSharePointTrigger.node.ts @@ -314,7 +314,7 @@ export class MicrosoftSharePointTrigger implements INodeType { this.getNode(), `The ${label} being watched is no longer reachable`, { - description: `Microsoft Graph returned 404 for ${label} ${targetId} on site ${siteId}. It may have been deleted or renamed, or the credential may have lost access to it. The saved position is kept, so polling resumes if it comes back.`, + description: `Microsoft Graph returned 404 for ${label} ${targetId} on site ${siteId}. The ${label} was most likely deleted. ${watchingList ? 'A rename causes this only when the list is given by title rather than by ID.' : 'A rename does not cause this, because the node watches by ID.'} A credential that lost access returns a permission error instead. The saved position is kept, so polling resumes if the ${label} comes back.`, }, ) : error; diff --git a/packages/nodes-base/nodes/Microsoft/SharePoint/test/MicrosoftSharePointTrigger.node.test.ts b/packages/nodes-base/nodes/Microsoft/SharePoint/test/MicrosoftSharePointTrigger.node.test.ts index a13e9b05506..85111443167 100644 --- a/packages/nodes-base/nodes/Microsoft/SharePoint/test/MicrosoftSharePointTrigger.node.test.ts +++ b/packages/nodes-base/nodes/Microsoft/SharePoint/test/MicrosoftSharePointTrigger.node.test.ts @@ -1,5 +1,11 @@ import { readFileSync } from 'fs'; -import type { IDataObject, INode, INodePropertyOptions, IPollFunctions } from 'n8n-workflow'; +import type { + IDataObject, + INode, + INodePropertyOptions, + IPollFunctions, + NodeOperationError, +} from 'n8n-workflow'; import { join } from 'path'; import { mock } from 'vitest-mock-extended'; @@ -21,6 +27,15 @@ vi.mock('../transport/delta', async (importOriginal) => ({ const deltaRequest = vi.mocked(microsoftApiRequestDelta); +const failureDescription = async (poll: () => Promise) => { + try { + await poll(); + } catch (error) { + return (error as NodeOperationError).description; + } + throw new Error('expected the poll to fail'); +}; + describe('Microsoft SharePoint Trigger', () => { const { description } = new MicrosoftSharePointTrigger(); @@ -357,6 +372,30 @@ describe('Microsoft SharePoint Trigger', () => { await expect(poll()).rejects.toThrow('no longer reachable'); }); + // A 404 means Graph could not find the ID. A rename keeps the ID, and a lost + // permission answers 403, so neither reaches this branch. Saying otherwise + // sends the reader to check the wrong thing. + it('blames deletion rather than a rename or a lost permission', async () => { + const { poll } = pollSetup({ mode: 'manual' }); + deltaRequest.mockRejectedValue(Object.assign(new Error('Not Found'), { statusCode: 404 })); + + const reason = await failureDescription(poll); + + expect(reason).toContain('most likely deleted'); + expect(reason).toContain('A rename does not cause this'); + expect(reason).toContain('returns a permission error instead'); + }); + + it('says a rename matters for a list given by title, and never for a library', async () => { + deltaRequest.mockRejectedValue(Object.assign(new Error('Not Found'), { statusCode: 404 })); + + const list = await failureDescription(pollSetup({ mode: 'manual', resource: 'list' }).poll); + const library = await failureDescription(pollSetup({ mode: 'manual' }).poll); + + expect(list).toContain('given by title'); + expect(library).not.toContain('given by title'); + }); + it('goes quiet while one failure persists, rather than failing every poll', async () => { const { ctx, state, poll } = pollSetup(); deltaRequest.mockRejectedValue(new Error('Service Unavailable'));