Skip to content

Refactor files for channel property management + validation - #3

Merged
hitherejoe merged 3 commits into
masterfrom
fix-channel-validation
Jul 27, 2026
Merged

Refactor files for channel property management + validation#3
hitherejoe merged 3 commits into
masterfrom
fix-channel-validation

Conversation

@hitherejoe

Copy link
Copy Markdown
Contributor

Refactors the files used for handling network validation, splitting things out so that they are more maintainable. We've also fixed support for various properties for several networks.

Fixes issues #1 and #2

@bufferapp bufferapp deleted a comment from coderabbitai Bot Jul 26, 2026
@hitherejoe
hitherejoe merged commit 6dc91b8 into master Jul 27, 2026
1 of 2 checks passed
@coderabbitai

coderabbitai Bot commented Jul 27, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Summary by CodeRabbit

  • New Features
    • Added Buffer support for creating ideas and richer posts across Facebook, Instagram, Google Business, Pinterest, YouTube, TikTok, Mastodon, Start Page, Threads, Bluesky, LinkedIn, Twitter/X, and more.
    • Added network-specific post options, metadata, attachments, scheduling, and media validation.
    • Added dynamic organization, channel, and Pinterest board selection.
  • Bug Fixes
    • Improved handling of disconnected channels, invalid URLs, unsupported media, scheduling rules, and API errors.
  • Tests
    • Added comprehensive coverage for post creation, credentials, batching, error handling, and network-specific behavior.

Walkthrough

The Buffer node was refactored into modular descriptions, GraphQL transport, load-option providers, resource actions, scheduling logic, network-specific validators, metadata builders, and shared date/URL helpers. The node now delegates idea and post execution to these modules while preserving per-item error handling. Jest configuration, development dependencies, execution helpers, and broad tests were added for credentials, transport, loading options, batching, errors, scheduling, asset validation, idea creation, and multiple social network post formats.

🚥 Pre-merge checks | ✅ 4
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly reflects the main refactor of channel property management and validation across the Buffer node.
Description check ✅ Passed The description matches the change set by describing the refactor and added network property support.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 5

🧹 Nitpick comments (7)
nodes/Buffer/Buffer.node.ts (1)

52-62: 🎯 Functional Correctness | 🔵 Trivial | 💤 Low value

Consider reading resource/operation per item.

They're resolved once at index 0, so expression-driven values (={{ $json.resource }}) would be applied uniformly across all items. Moving the reads inside the loop costs nothing and keeps per-item semantics consistent with the rest of the action code, which already resolves every parameter at itemIndex.

♻️ Proposed refactor
-		const resource = this.getNodeParameter('resource', 0) as string;
-		const operation = this.getNodeParameter('operation', 0) as string;
-
 		const apiUrl = await getApiUrl(this);
 
 		for (let i = 0; i < items.length; i++) {
 			try {
+				const resource = this.getNodeParameter('resource', i) as string;
+				const operation = this.getNodeParameter('operation', i) as string;
 				if (resource === 'idea') {
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@nodes/Buffer/Buffer.node.ts` around lines 52 - 62, Move the `resource` and
`operation` parameter reads into the per-item loop in the node’s execution
method, resolving both with the current loop index `i` before the dispatch
branches. Keep the existing createIdea/createPost handling, but ensure
expression-driven values are evaluated independently for each item instead of
being reused from index 0.
nodes/Buffer/helpers/urlValidation.ts (1)

4-11: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Enforce requiredLabel at the type level when required: true.

The docstring says requiredLabel is required when required is true, but the type still allows it to be omitted, so a misconfigured caller gets "undefined is required and cannot be empty" instead of a compile error. This helper will back every network's asset validation in the next layer, so getting the contract right now avoids copy-pasted mistakes later.

♻️ Proposed fix
-interface ValidateUrlOptions {
-	/** Whether an empty value should throw. When false, an empty value is silently accepted (skips validation). */
-	required: boolean;
-	/** Label used in the "is required and cannot be empty" message, e.g. "Image URL". Required when `required` is true. */
-	requiredLabel?: string;
-	/** Label used in the "Invalid <label> URL format/protocol" messages, e.g. "image", "video", "thumbnail". */
-	formatLabel: string;
-}
+type ValidateUrlOptions =
+	| { required: true; requiredLabel: string; formatLabel: string }
+	| { required: false; requiredLabel?: string; formatLabel: string };
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@nodes/Buffer/helpers/urlValidation.ts` around lines 4 - 11, Make
ValidateUrlOptions a discriminated union so required: true requires a
non-optional requiredLabel, while required: false permits requiredLabel to
remain optional. Preserve formatLabel as mandatory and ensure callers receive a
compile-time error when required is true without its label.
nodes/Buffer/descriptions/post.description.ts (1)

88-99: 📐 Maintainability & Code Quality | 🔵 Trivial | 🏗️ Heavy lift

Casing-variant channelService arrays duplicated across six description files.

Root cause: channelService's default expression takes the raw, un-normalized value from the channel API response, so every file that gates a field on it must hand-enumerate every casing/underscore variant (e.g. google/Google/GOOGLE/google_business/...). Normalizing once at the source (e.g. append .toLowerCase() to the default expression) and extracting one shared constants module (e.g. GOOGLE_BUSINESS_SERVICE, FACEBOOK_SERVICE) would collapse dozens of repeated literals and remove the risk of a missed/typo'd variant silently breaking a field's visibility for a whole network.

  • nodes/Buffer/descriptions/post.description.ts#L88-L99: normalize the channelService default expression's casing (root cause); also update its own repeated arrays at L202-213, L226-236, L269-284, L296-305, L322-331, L343-352 to use the shared constant.
  • nodes/Buffer/descriptions/google-business.description.ts#L25-L31: replace all 13 repeated 9-variant arrays with one shared GOOGLE_BUSINESS_SERVICE constant.
  • nodes/Buffer/descriptions/facebook.description.ts#L25-L34: replace the 3 repeated channelService/channelType arrays with shared constants.
  • nodes/Buffer/descriptions/instagram.description.ts#L25-L37: replace the 4 repeated channelService/channelType arrays with shared constants.
  • nodes/Buffer/descriptions/pinterest.description.ts#L12-L21: replace the 3 repeated channelService arrays with a shared constant.
  • nodes/Buffer/descriptions/youtube.description.ts#L8-L20: replace the 7 repeated channelService arrays with a shared constant.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@nodes/Buffer/descriptions/post.description.ts` around lines 88 - 99,
Normalize the channelService default expression in post.description.ts and
extract shared service constants, including GOOGLE_BUSINESS_SERVICE and
FACEBOOK_SERVICE, for reuse. Replace the repeated channelService/channelType
variant arrays in nodes/Buffer/descriptions/post.description.ts (L202-213,
L226-236, L269-284, L296-305, L322-331, L343-352),
nodes/Buffer/descriptions/google-business.description.ts (L25-31),
nodes/Buffer/descriptions/facebook.description.ts (L25-34),
nodes/Buffer/descriptions/instagram.description.ts (L25-37),
nodes/Buffer/descriptions/pinterest.description.ts (L12-21), and
nodes/Buffer/descriptions/youtube.description.ts (L8-20) with the appropriate
shared constants; ensure all existing visibility behavior is preserved without
casing or underscore variants duplicated locally.
nodes/Buffer/actions/post/create.ts (2)

59-61: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Duplicated Google Business alias list.

['google', 'googlebusiness', 'google_business'] is repeated at both call sites. Consider exporting a helper (e.g. isGoogleBusinessChannel(channelService)) from networks/google-business.ts, which already owns Google Business logic, to keep the alias list in one place.

Also applies to: 95-96

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@nodes/Buffer/actions/post/create.ts` around lines 59 - 61, Centralize the
Google Business channel alias list by adding an exported helper such as
isGoogleBusinessChannel in networks/google-business.ts. Update both call sites
in the create flow, including the secondary location around the alternate
referenced lines, to use this helper instead of maintaining inline alias arrays.

205-215: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Duplicated GraphQL response error-handling logic. Both actions repeat the same response.errors / result?.message checks; extracting a shared helper (e.g. in transport/graphql.ts) would centralize this for future network actions.

  • nodes/Buffer/actions/post/create.ts#L205-L215: replace with a call to a shared checkGraphQLResult(ctx, response, 'createPost', itemIndex) style helper.
  • nodes/Buffer/actions/idea/create.ts#L79-L89: replace with the same shared helper, passing 'createIdea'.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@nodes/Buffer/actions/post/create.ts` around lines 205 - 215, Extract the
duplicated GraphQL error checks into a shared helper in transport/graphql.ts,
such as checkGraphQLResult, preserving both response.errors and mutation-level
message handling while accepting the node context, response, mutation key, and
item index. Replace the checks in nodes/Buffer/actions/post/create.ts lines
205-215 with the helper using createPost, and in
nodes/Buffer/actions/idea/create.ts lines 79-89 with the helper using
createIdea.
nodes/Buffer/actions/post/networks/google-business.ts (2)

20-89: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

Optional link/URL metadata fields bypass the validateUrl helper used for asset URLs. create.ts validates image/video asset URLs via validateUrl, but the analogous "link" fields in network metadata builders skip this check entirely, letting malformed URLs reach the API unvalidated.

  • nodes/Buffer/actions/post/networks/google-business.ts#L20-L89: validate googleWhatsNewLink, googleOfferLink, and googleEventLink with validateUrl (non-required) before assigning them.
  • nodes/Buffer/actions/post/networks/facebook.ts#L13-L20: validate facebookLinkAttachment with validateUrl before assigning to meta.linkAttachment.url.
  • nodes/Buffer/actions/post/networks/instagram.ts#L27-L39: validate instagramLink with validateUrl before assigning to meta.link.
  • nodes/Buffer/actions/post/networks/pinterest.ts#L36-L53: validate pinterestUrl with validateUrl before assigning to meta.url.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@nodes/Buffer/actions/post/networks/google-business.ts` around lines 20 - 89,
Validate each optional network link before assignment using the existing
non-required validateUrl helper. In
nodes/Buffer/actions/post/networks/google-business.ts lines 20-89, validate
googleWhatsNewLink, googleOfferLink, and googleEventLink; apply the same change
to facebookLinkAttachment in nodes/Buffer/actions/post/networks/facebook.ts
lines 13-20, instagramLink in nodes/Buffer/actions/post/networks/instagram.ts
lines 27-39, and pinterestUrl in nodes/Buffer/actions/post/networks/pinterest.ts
lines 36-53, preserving omission of unset optional values.

20-26: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

Google Business link fields (googleWhatsNewLink, googleOfferLink, googleEventLink) bypass URL validation.

Same gap as other network builders — these links skip the validateUrl helper used for asset URLs.

Also applies to: 27-44, 45-89

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@nodes/Buffer/actions/post/networks/google-business.ts` around lines 20 - 26,
Update the Google Business post-building branches for googleWhatsNewLink,
googleOfferLink, and googleEventLink to pass supplied links through the existing
validateUrl helper before assigning them to the corresponding metadata details.
Preserve the current optional-field behavior and ensure invalid URLs are handled
consistently with other asset URL fields.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@nodes/Buffer/__tests__/google-business.test.ts`:
- Around line 190-202: Strengthen the test “should combine the date and time
when a specific event time is provided” by overriding googleEventStartDate and
googleEventEndDate with dates whose embedded times differ from 10:00 and 18:00,
then update the expected ISO timestamps to reflect the supplied
googleEventStartTime and googleEventEndTime. Keep the isFullDayEvent assertion
unchanged.

In `@nodes/Buffer/actions/post/create.ts`:
- Around line 146-150: Validate dueAt in the customScheduled branch before
calling toISOString, and convert invalid date values into a NodeOperationError
that includes the current item index. Keep assigning the normalized ISO string
to input.dueAt for valid dates, matching the validation behavior used elsewhere
in the create flow.

In `@nodes/Buffer/actions/post/networks/google-business.ts`:
- Around line 27-44: Add required-value validation in the Google Business offer
and event branches before processing title, startDate, or endDate. Reuse the
existing validation pattern used for event times, and ensure missing or blank
fields are rejected before calling toDateOnlyIso or combineDateAndTime; preserve
the existing metadata construction for valid values.

In `@nodes/Buffer/helpers/dateTime.ts`:
- Around line 5-14: Update toDateOnlyIso and combineDateAndTime to validate
parsed dates before calling toISOString, and throw a descriptive catchable error
for invalid dateStr values. In combineDateAndTime, also validate timeStr with
the existing TIME_FORMAT_REGEX before constructing the timestamp, preserving
normal ISO output for valid inputs.

In `@nodes/Buffer/transport/graphql.ts`:
- Around line 12-27: Update executeGraphQL and the load-options consumers
getOrganizations, getChannels, and getPinterestBoards so GraphQL responses
containing errors are surfaced as failures before response.data is read, rather
than being converted to empty arrays. Preserve normal data handling for
successful responses and use the existing request error-handling conventions.

---

Nitpick comments:
In `@nodes/Buffer/actions/post/create.ts`:
- Around line 59-61: Centralize the Google Business channel alias list by adding
an exported helper such as isGoogleBusinessChannel in
networks/google-business.ts. Update both call sites in the create flow,
including the secondary location around the alternate referenced lines, to use
this helper instead of maintaining inline alias arrays.
- Around line 205-215: Extract the duplicated GraphQL error checks into a shared
helper in transport/graphql.ts, such as checkGraphQLResult, preserving both
response.errors and mutation-level message handling while accepting the node
context, response, mutation key, and item index. Replace the checks in
nodes/Buffer/actions/post/create.ts lines 205-215 with the helper using
createPost, and in nodes/Buffer/actions/idea/create.ts lines 79-89 with the
helper using createIdea.

In `@nodes/Buffer/actions/post/networks/google-business.ts`:
- Around line 20-89: Validate each optional network link before assignment using
the existing non-required validateUrl helper. In
nodes/Buffer/actions/post/networks/google-business.ts lines 20-89, validate
googleWhatsNewLink, googleOfferLink, and googleEventLink; apply the same change
to facebookLinkAttachment in nodes/Buffer/actions/post/networks/facebook.ts
lines 13-20, instagramLink in nodes/Buffer/actions/post/networks/instagram.ts
lines 27-39, and pinterestUrl in nodes/Buffer/actions/post/networks/pinterest.ts
lines 36-53, preserving omission of unset optional values.
- Around line 20-26: Update the Google Business post-building branches for
googleWhatsNewLink, googleOfferLink, and googleEventLink to pass supplied links
through the existing validateUrl helper before assigning them to the
corresponding metadata details. Preserve the current optional-field behavior and
ensure invalid URLs are handled consistently with other asset URL fields.

In `@nodes/Buffer/Buffer.node.ts`:
- Around line 52-62: Move the `resource` and `operation` parameter reads into
the per-item loop in the node’s execution method, resolving both with the
current loop index `i` before the dispatch branches. Keep the existing
createIdea/createPost handling, but ensure expression-driven values are
evaluated independently for each item instead of being reused from index 0.

In `@nodes/Buffer/descriptions/post.description.ts`:
- Around line 88-99: Normalize the channelService default expression in
post.description.ts and extract shared service constants, including
GOOGLE_BUSINESS_SERVICE and FACEBOOK_SERVICE, for reuse. Replace the repeated
channelService/channelType variant arrays in
nodes/Buffer/descriptions/post.description.ts (L202-213, L226-236, L269-284,
L296-305, L322-331, L343-352),
nodes/Buffer/descriptions/google-business.description.ts (L25-31),
nodes/Buffer/descriptions/facebook.description.ts (L25-34),
nodes/Buffer/descriptions/instagram.description.ts (L25-37),
nodes/Buffer/descriptions/pinterest.description.ts (L12-21), and
nodes/Buffer/descriptions/youtube.description.ts (L8-20) with the appropriate
shared constants; ensure all existing visibility behavior is preserved without
casing or underscore variants duplicated locally.

In `@nodes/Buffer/helpers/urlValidation.ts`:
- Around line 4-11: Make ValidateUrlOptions a discriminated union so required:
true requires a non-optional requiredLabel, while required: false permits
requiredLabel to remain optional. Preserve formatLabel as mandatory and ensure
callers receive a compile-time error when required is true without its label.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 6b23d38e-3886-46c2-8dc7-35e7acc9ad4d

📥 Commits

Reviewing files that changed from the base of the PR and between 6d73858 and 8d79745.

⛔ Files ignored due to path filters (1)
  • package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (47)
  • jest.config.js
  • nodes/Buffer/Buffer.node.ts
  • nodes/Buffer/__tests__/batch.test.ts
  • nodes/Buffer/__tests__/bluesky.test.ts
  • nodes/Buffer/__tests__/continueOnFail.test.ts
  • nodes/Buffer/__tests__/core.test.ts
  • nodes/Buffer/__tests__/credentials.test.ts
  • nodes/Buffer/__tests__/errors.test.ts
  • nodes/Buffer/__tests__/facebook.test.ts
  • nodes/Buffer/__tests__/google-business.test.ts
  • nodes/Buffer/__tests__/graphql.test.ts
  • nodes/Buffer/__tests__/idea.test.ts
  • nodes/Buffer/__tests__/instagram.test.ts
  • nodes/Buffer/__tests__/linkedin.test.ts
  • nodes/Buffer/__tests__/loadOptions.test.ts
  • nodes/Buffer/__tests__/mastodon.test.ts
  • nodes/Buffer/__tests__/pinterest.test.ts
  • nodes/Buffer/__tests__/startpage.test.ts
  • nodes/Buffer/__tests__/test-helpers.ts
  • nodes/Buffer/__tests__/threads.test.ts
  • nodes/Buffer/__tests__/tiktok.test.ts
  • nodes/Buffer/__tests__/twitter.test.ts
  • nodes/Buffer/__tests__/youtube.test.ts
  • nodes/Buffer/actions/idea/create.ts
  • nodes/Buffer/actions/post/create.ts
  • nodes/Buffer/actions/post/networks/facebook.ts
  • nodes/Buffer/actions/post/networks/google-business.ts
  • nodes/Buffer/actions/post/networks/instagram.ts
  • nodes/Buffer/actions/post/networks/mastodon.ts
  • nodes/Buffer/actions/post/networks/pinterest.ts
  • nodes/Buffer/actions/post/networks/startpage.ts
  • nodes/Buffer/actions/post/networks/tiktok.ts
  • nodes/Buffer/actions/post/networks/youtube.ts
  • nodes/Buffer/actions/post/scheduling.ts
  • nodes/Buffer/descriptions/facebook.description.ts
  • nodes/Buffer/descriptions/google-business.description.ts
  • nodes/Buffer/descriptions/idea.description.ts
  • nodes/Buffer/descriptions/index.ts
  • nodes/Buffer/descriptions/instagram.description.ts
  • nodes/Buffer/descriptions/pinterest.description.ts
  • nodes/Buffer/descriptions/post.description.ts
  • nodes/Buffer/descriptions/youtube.description.ts
  • nodes/Buffer/helpers/dateTime.ts
  • nodes/Buffer/helpers/urlValidation.ts
  • nodes/Buffer/methods/loadOptions.ts
  • nodes/Buffer/transport/graphql.ts
  • package.json

Comment on lines +190 to +202
it('should combine the date and time when a specific event time is provided', async () => {
const input = await executePostCreate({
...googleEventDefaults,
googleEventHasTime: true,
googleEventStartTime: '10:00',
googleEventEndTime: '18:00',
});
const meta = (input.metadata as IDataObject).google as IDataObject;
const details = meta.detailsEvent as IDataObject;
expect(details.startDate).toBe('2026-07-01T10:00:00.000Z');
expect(details.endDate).toBe('2026-07-01T18:00:00.000Z');
expect(details.isFullDayEvent).toBe(false);
});

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Test can't detect a broken time-combination.

googleEventStartTime/googleEventEndTime ('10:00'/'18:00') coincidentally match the time already embedded in the default googleEventStartDate/googleEventEndDate. A bug that ignores the time overrides entirely (just passing the raw date through) would still satisfy the startDate/endDate assertions here.

Suggested fix: use dates whose default time differs from the override
 		it('should combine the date and time when a specific event time is provided', async () => {
 			const input = await executePostCreate({
 				...googleEventDefaults,
+				googleEventStartDate: '2026-07-01T00:00:00Z',
+				googleEventEndDate: '2026-07-01T00:00:00Z',
 				googleEventHasTime: true,
 				googleEventStartTime: '10:00',
 				googleEventEndTime: '18:00',
 			});
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
it('should combine the date and time when a specific event time is provided', async () => {
const input = await executePostCreate({
...googleEventDefaults,
googleEventHasTime: true,
googleEventStartTime: '10:00',
googleEventEndTime: '18:00',
});
const meta = (input.metadata as IDataObject).google as IDataObject;
const details = meta.detailsEvent as IDataObject;
expect(details.startDate).toBe('2026-07-01T10:00:00.000Z');
expect(details.endDate).toBe('2026-07-01T18:00:00.000Z');
expect(details.isFullDayEvent).toBe(false);
});
it('should combine the date and time when a specific event time is provided', async () => {
const input = await executePostCreate({
...googleEventDefaults,
googleEventStartDate: '2026-07-01T00:00:00Z',
googleEventEndDate: '2026-07-01T00:00:00Z',
googleEventHasTime: true,
googleEventStartTime: '10:00',
googleEventEndTime: '18:00',
});
const meta = (input.metadata as IDataObject).google as IDataObject;
const details = meta.detailsEvent as IDataObject;
expect(details.startDate).toBe('2026-07-01T10:00:00.000Z');
expect(details.endDate).toBe('2026-07-01T18:00:00.000Z');
expect(details.isFullDayEvent).toBe(false);
});
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@nodes/Buffer/__tests__/google-business.test.ts` around lines 190 - 202,
Strengthen the test “should combine the date and time when a specific event time
is provided” by overriding googleEventStartDate and googleEventEndDate with
dates whose embedded times differ from 10:00 and 18:00, then update the expected
ISO timestamps to reflect the supplied googleEventStartTime and
googleEventEndTime. Keep the isFullDayEvent assertion unchanged.

Comment on lines +146 to +150
if (shareMode === 'customScheduled') {
const dueAt = ctx.getNodeParameter('dueAt', i) as string;
// Ensure ISO 8601 format with timezone
input.dueAt = new Date(dueAt).toISOString();
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Unvalidated dueAt can throw an unlabeled RangeError.

If dueAt isn't a parseable date string, new Date(dueAt).toISOString() throws RangeError: Invalid time value instead of a NodeOperationError with itemIndex context, unlike every other validation failure in this file.

🐛 Proposed fix
 	if (shareMode === 'customScheduled') {
 		const dueAt = ctx.getNodeParameter('dueAt', i) as string;
-		// Ensure ISO 8601 format with timezone
-		input.dueAt = new Date(dueAt).toISOString();
+		const parsedDueAt = new Date(dueAt);
+		if (isNaN(parsedDueAt.getTime())) {
+			throw new NodeOperationError(
+				ctx.getNode(),
+				`Invalid "Due At" date: "${dueAt}" could not be parsed.`,
+				{ itemIndex: i },
+			);
+		}
+		// Ensure ISO 8601 format with timezone
+		input.dueAt = parsedDueAt.toISOString();
 	}
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if (shareMode === 'customScheduled') {
const dueAt = ctx.getNodeParameter('dueAt', i) as string;
// Ensure ISO 8601 format with timezone
input.dueAt = new Date(dueAt).toISOString();
}
if (shareMode === 'customScheduled') {
const dueAt = ctx.getNodeParameter('dueAt', i) as string;
const parsedDueAt = new Date(dueAt);
if (isNaN(parsedDueAt.getTime())) {
throw new NodeOperationError(
ctx.getNode(),
`Invalid "Due At" date: "${dueAt}" could not be parsed.`,
{ itemIndex: i },
);
}
// Ensure ISO 8601 format with timezone
input.dueAt = parsedDueAt.toISOString();
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@nodes/Buffer/actions/post/create.ts` around lines 146 - 150, Validate dueAt
in the customScheduled branch before calling toISOString, and convert invalid
date values into a NodeOperationError that includes the current item index. Keep
assigning the normalized ISO string to input.dueAt for valid dates, matching the
validation behavior used elsewhere in the create flow.

Comment on lines +27 to +44
} else if (googlePostType === 'offer') {
const title = ctx.getNodeParameter('googleOfferTitle', itemIndex) as string;
const startDate = ctx.getNodeParameter('googleOfferStartDate', itemIndex) as string;
const endDate = ctx.getNodeParameter('googleOfferEndDate', itemIndex) as string;
const code = ctx.getNodeParameter('googleOfferCode', itemIndex) as string;
const link = ctx.getNodeParameter('googleOfferLink', itemIndex) as string;
const terms = ctx.getNodeParameter('googleOfferTerms', itemIndex) as string;
if (title) googleMeta.title = title;
const details: IDataObject = {
title,
// Google Business offers only support a calendar date, not a time of day
startDate: toDateOnlyIso(startDate),
endDate: toDateOnlyIso(endDate),
};
if (code) details.code = code;
if (link) details.link = link;
if (terms) details.terms = terms;
googleMeta.detailsOffer = details;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
rg -n -B2 -A6 "googleOfferTitle|googleOfferStartDate|googleOfferEndDate|googleEventTitle|googleEventStartDate|googleEventEndDate" nodes/Buffer/descriptions/google-business.description.ts

Repository: bufferapp/buffer-n8n

Length of output: 2355


Add presence checks for the Google Business date/title fields. Offer and event title, startDate, and endDate are read without any required-field validation, so blank values can still flow into toDateOnlyIso(...) / combineDateAndTime(...). That should be guarded the same way event times already are.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@nodes/Buffer/actions/post/networks/google-business.ts` around lines 27 - 44,
Add required-value validation in the Google Business offer and event branches
before processing title, startDate, or endDate. Reuse the existing validation
pattern used for event times, and ensure missing or blank fields are rejected
before calling toDateOnlyIso or combineDateAndTime; preserve the existing
metadata construction for valid values.

Comment on lines +5 to +14
export function toDateOnlyIso(dateStr: string): string {
return `${new Date(dateStr).toISOString().slice(0, 10)}T00:00:00.000Z`;
}

// Combines a calendar date with a separately-provided "HH:mm" time into a single ISO timestamp,
// used for Google Business Events when the user opts in to specifying a start/end time.
export function combineDateAndTime(dateStr: string, timeStr: string): string {
const datePart = new Date(dateStr).toISOString().slice(0, 10);
return new Date(`${datePart}T${timeStr}:00.000Z`).toISOString();
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win

Unguarded date/time parsing can throw an uncaught RangeError.

Neither helper validates its input before calling .toISOString(). Since these values can originate from expressions (not just the date-picker UI), a malformed dateStr/timeStr produces an Invalid Date and an uncaught RangeError rather than a descriptive, catchable error — TIME_FORMAT_REGEX is defined right here but isn't used to guard combineDateAndTime.

🛡️ Proposed fix
 export function combineDateAndTime(dateStr: string, timeStr: string): string {
+	if (!TIME_FORMAT_REGEX.test(timeStr)) {
+		throw new Error(`Invalid time "${timeStr}": expected HH:mm format`);
+	}
 	const datePart = new Date(dateStr).toISOString().slice(0, 10);
 	return new Date(`${datePart}T${timeStr}:00.000Z`).toISOString();
 }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@nodes/Buffer/helpers/dateTime.ts` around lines 5 - 14, Update toDateOnlyIso
and combineDateAndTime to validate parsed dates before calling toISOString, and
throw a descriptive catchable error for invalid dateStr values. In
combineDateAndTime, also validate timeStr with the existing TIME_FORMAT_REGEX
before constructing the timestamp, preserving normal ISO output for valid
inputs.

Comment on lines +12 to +27
export async function executeGraphQL(
ctx: IExecuteFunctions | ILoadOptionsFunctions,
apiUrl: string,
query: string,
variables?: Record<string, unknown>,
) {
return ctx.helpers.httpRequestWithAuthentication.call(ctx, 'bufferApi', {
method: 'POST' as IHttpRequestMethods,
url: apiUrl,
headers: {
'Content-Type': 'application/json',
},
body: variables !== undefined ? { query, variables } : { query },
json: true,
});
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Inspect the shared GraphQL transport
ast-grep outline nodes/Buffer/transport/graphql.ts --view expanded

# Inspect its callers
rg -n "executeGraphQL\\(" nodes/Buffer -S
rg -n "httpRequestWithAuthentication\\.call\\(ctx, 'bufferApi'" nodes/Buffer -S

# Read the transport and the relevant callers with line numbers
cat -n nodes/Buffer/transport/graphql.ts
printf '\n---\n'
sed -n '1,220p' nodes/Buffer/loadOptions.ts | cat -n
printf '\n---\n'
sed -n '1,260p' nodes/Buffer/Buffer.node.ts | cat -n

Repository: bufferapp/buffer-n8n

Length of output: 2699


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Map the actual Buffer node files
git ls-files nodes/Buffer | sort

printf '\n--- loadOptions ---\n'
cat -n nodes/Buffer/methods/loadOptions.ts

printf '\n--- idea create ---\n'
sed -n '1,180p' nodes/Buffer/actions/idea/create.ts | cat -n

printf '\n--- post create ---\n'
sed -n '160,260p' nodes/Buffer/actions/post/create.ts | cat -n

printf '\n--- graphql tests ---\n'
cat -n nodes/Buffer/__tests__/graphql.test.ts

printf '\n--- search for timeout usage in Buffer node ---\n'
rg -n "timeout\s*:" nodes/Buffer -S

printf '\n--- search for GraphQL errors handling ---\n'
rg -n "errors\\b|response\\.errors|response\\.data\\?" nodes/Buffer -S

Repository: bufferapp/buffer-n8n

Length of output: 12776


🌐 Web query:

n8n httpRequestWithAuthentication timeout request options IHttpRequestOptions documentation

💡 Result:

In n8n, when using httpRequestWithAuthentication in custom node development, the timeout property is a member of the IHttpRequestOptions interface [1][2][3]. The timeout property accepts a value in milliseconds [1][2]. It defines how long the n8n execution engine should wait for the server to send response headers and start sending the response body before aborting the request [4][5][6]. According to the IHttpRequestOptions interface definition, the timeout property is typed as a number [3]: { //... other properties timeout?: number; //... } When calling httpRequestWithAuthentication, you pass an object conforming to IHttpRequestOptions as the third argument [1][2][7]: const response = await this.helpers.httpRequestWithAuthentication.call( this, 'credentialTypeName', { url: 'https://api.example.com', method: 'GET', timeout: 10000, // Timeout set to 10 seconds (10,000 milliseconds) },); If the timeout is not explicitly provided in the request options, the system may apply a default timeout (which, in some internal n8n implementations, defaults to 300,000 milliseconds or 5 minutes) [8]. The httpRequestWithAuthentication helper passes these options to the underlying request execution logic, which ensures the timeout is applied to the request [9].

Citations:


Surface GraphQL errors in load-options queries. getOrganizations, getChannels, and getPinterestBoards fall back to [] when the response has GraphQL errors, so API/auth/query failures show up as empty dropdowns instead of an error. Handle response.errors before reading response.data.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@nodes/Buffer/transport/graphql.ts` around lines 12 - 27, Update
executeGraphQL and the load-options consumers getOrganizations, getChannels, and
getPinterestBoards so GraphQL responses containing errors are surfaced as
failures before response.data is read, rather than being converted to empty
arrays. Preserve normal data handling for successful responses and use the
existing request error-handling conventions.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant