From 9964c4a44486e3f7e28fd173adc5d5d00a1228aa Mon Sep 17 00:00:00 2001 From: falc1 Date: Thu, 24 Apr 2025 22:35:12 +0400 Subject: [PATCH 01/10] Initialization --- components/wordpress_com/common/methods.mjs | 478 ++++++++++++++++++ components/wordpress_com/common/utils.mjs | 24 + components/wordpress_com/package.json | 4 +- components/wordpress_com/tests/README.md | 44 ++ .../action-tests/worpress_com-create-post.mjs | 135 +++++ components/wordpress_com/tests/get-token.mjs | 80 +++ .../bogus-data/bogus-data-google-date.mjs | 113 +++++ .../methods/bogus-data/bogus-data-url.mjs | 111 ++++ .../tests/methods/test-checkIfUrlValid.mjs | 100 ++++ .../methods/test-throwIfNotYMDDashDate.mjs | 63 +++ .../wordpress_com/tests/mockery-dollar.mjs | 6 + .../wordpress_com/wordpress_com.app.mjs | 6 + 12 files changed, 1163 insertions(+), 1 deletion(-) create mode 100644 components/wordpress_com/common/methods.mjs create mode 100644 components/wordpress_com/common/utils.mjs create mode 100644 components/wordpress_com/tests/README.md create mode 100644 components/wordpress_com/tests/action-tests/worpress_com-create-post.mjs create mode 100644 components/wordpress_com/tests/get-token.mjs create mode 100644 components/wordpress_com/tests/methods/bogus-data/bogus-data-google-date.mjs create mode 100644 components/wordpress_com/tests/methods/bogus-data/bogus-data-url.mjs create mode 100644 components/wordpress_com/tests/methods/test-checkIfUrlValid.mjs create mode 100644 components/wordpress_com/tests/methods/test-throwIfNotYMDDashDate.mjs create mode 100644 components/wordpress_com/tests/mockery-dollar.mjs diff --git a/components/wordpress_com/common/methods.mjs b/components/wordpress_com/common/methods.mjs new file mode 100644 index 0000000000000..c02506ff997bc --- /dev/null +++ b/components/wordpress_com/common/methods.mjs @@ -0,0 +1,478 @@ + +export default { + +/* =================================================================================================== + Iterates through the provided props metadata and validates each input value. + + - Trims input strings using `trimIfString()`. + - Skips validation for optional fields that are undefined, null, or blank. + - Calls `validateUserInput()` for each defined input and accumulates any warnings. + + Parameters: + propsMeta (object): An object describing metadata for each prop. + context (object): The source of values to be validated (usually `this`). + + Returns: + Array: A flat array of warning messages (empty if no issues). +====================================================================================================== */ + +validationLoop(propsMeta, context){ + + const warnings = []; + + for (let propName in propsMeta){ + + const meta = propsMeta[propName]; + + // Trim the input if it's a string + context[propName] = this.trimIfString(context[propName]); + + // If the optional prop is undefined, null, or a blank string — skip it + if (meta.optional === true && ((context[propName] ?? '') === '')) continue; + + // Validate the input and accumulate the warning message if any. + warnings.push(...this.validateUserInput(meta, context[propName])); + + console.log("SUCCESS"); + + + }; + + return warnings; + +}, + +/* =================================================================================================== + Calls the appropriate validation method based on the input's type. + Uses either the standard `type` or an extended override via `extendedType`. + Throws if no validator is found or if the value fails validation. +====================================================================================================== */ + +validateUserInput(propMeta, value){ + + this.throwIfNotObject(propMeta, propMeta.label); + + const warnings = []; + + // Check if there is extra type in prop meta. Like string can be also date type + const type = ("extendedType" in propMeta) ? propMeta.extendedType : propMeta.type; + + // Decide which method to call based on the input we want to check. + + const validators = { + "string": this.throwIfBlankOrNotString, + "integer": this.throwIfNotWholeNumber, + "string[]": this.throwIfNotArrayOfStrings, + "object": this.checkObject, + "array": this.throwIfNotArray, + "YYYY-MM-DD": this.throwIfNotYMDDashDate, + "url": this.checkIfUrlValid, + "domainOrId" : this.checkDomainOrId, + }; + + const validator = validators[type]; + + if (!validator) { + this.throwCustomError(`No validator found for type "${type}"`, propMeta.label); + }; + + // Calls the correct function and passes label + const validationResult = validator.call(this, value, propMeta.label); + + // Just to double-check: we're expecting an array here. + // Helps catch developer mistakes early. + if (validationResult){ + this.throwIfNotArray(validationResult); + warnings.push(...validationResult); + }; + + // Returns empty array if no warnings. + return warnings; +}, + + + +/* =================================================================================================== + Builds a payload object using only the props marked for inclusion in the POST body. + + Parameters: + propsMeta (object): Metadata for each prop, including the `postBody` flag. + context (object): Typically `this`; contains raw user input values that may or may not be validated. + It is advised to call this function only after running `validationLoop()` to ensure inputs are trimmed and validated. + + Returns: + Object: A payload object containing only the props that should be sent in the request body. +====================================================================================================== */ + +preparePayload(propsMeta, context) { + + const payload = {}; + + for (const propName in propsMeta) { + + // If the optional prop is undefined, null, or a blank string — skip it + if ( propsMeta[propName].optional === true && ((context[propName] ?? '') === '')) continue; + + // If the prop is meant for the payload - add it. + if (propsMeta[propName].postBody === true) { + payload[propName] = context[propName]; + }; + } + + return payload; +}, + + + + +/* =================================================================================================== + Return the trimmed string or input as is if its not a string +====================================================================================================== */ +// +trimIfString(input) { + return (typeof input === 'string') ? input.trim() : input; +}, + +/* =================================================================================================== + Throws a custom error and marks it with a flag (`isCustom`) for easier debugging and testing. +====================================================================================================== */ + + throwCustomError(msg){ + const err = new Error(msg); + err.isCustom = true; + throw err; + }, + +/* =================================================================================================== + Determines whether an error originated from your own validation code or from the API request. + Useful for debugging and crafting more helpful error messages. +====================================================================================================== */ + + // ===================================================================== + checkWhoThrewError(error) { + return { whoThrew: error?.response?.status ? "API response" : "Internal Code", error, }; + }, + +/* =================================================================================================== + Throws if the input is not a string or is a blank string (only whitespace, tabs, newlines, etc.). +====================================================================================================== */ + + throwIfBlankOrNotString(input, reason) { + + if (typeof input !== "string") { + this.throwCustomError( + `Expected a string, but got ${typeof input}` + this._reasonMsg(reason) + ); + } + if (input.trim() === "") { + this.throwCustomError( + "Expected a non-empty string with visible characters." + this._reasonMsg(reason) + ); + } + }, + + +/* =================================================================================================== + Throws if the input is not a JSON. +====================================================================================================== */ + +throwIfNotJSON(input, reason) { + try { + return JSON.parse(input); + } catch (err) { + this.throwCustomError( `Can not parse JSON. Error : ${err}` + this._reasonMsg(reason)) + }; +}, + +/* =================================================================================================== + Throws if the input is not a whole number (non-negative integer). +====================================================================================================== */ + + throwIfNotWholeNumber(input, reason) { + if (!Number.isInteger(input) || input < 0) { + this.throwCustomError( `Expected a whole number (0 or positive integer), but got ${typeof input}` + this._reasonMsg(reason)); + }; + }, + +/* =================================================================================================== + Throws if not string or if not whole number. +====================================================================================================== */ + +throwIfNotStrOrId(input, reason) { + if (typeof input === "number") { + this.throwIfNotWholeNumber(input, reason); + return; + } + + if (typeof input === "string") { + this.throwIfBlankOrNotString(input, reason); + return; + } + + this.throwCustomError( + `Expected a non-empty string or a whole number. but got ${typeof input}` + this._reasonMsg(reason) + ); +}, + + +/* =================================================================================================== + Throws if the input is not an array. +====================================================================================================== */ + + + throwIfNotArray(input, reason) { + + if (!Array.isArray(input)) { + this.throwCustomError(`Invalid argument type. Expected an array ` + this._reasonMsg(reason)); + }; + }, + +/* =================================================================================================== + Throws if the input is not an array of strings. +====================================================================================================== */ + + throwIfNotArrayOfStrings(input, reason){ + + this.throwIfNotArray(input, reason); + + for (let i = 0; i < input.length; i++) { + if (typeof input[i] !== "string"){ + this.throwCustomError(`Expected an array of strings ` + this._reasonMsg(reason)); + } + }; + }, + + +/* =================================================================================================== + Throws if the input is not a plain object (arrays are excluded). +====================================================================================================== */ + + throwIfNotObject(input, reason) { + + if( typeof input === "object" && input !== null && !Array.isArray(input)){ + return input; + }; + + this.throwCustomError(`Invalid argument type. Expected an object ` + this._reasonMsg(reason)); + }, + + +/* =================================================================================================== + Throws if the input is neither an object nor an array. +====================================================================================================== */ + +throwIfNotObjectOrArray(input, reason) { + + if( typeof input === "object" && input !== null){ + return input; + }; + this.throwCustomError(`Invalid argument type. Expected an object or array` + this._reasonMsg(reason)); +}, + + + +/* =================================================================================================== + Validates that a string follows the YYYY-MM-DD date format. + Throws if the format or any individual part (year, month, day) is invalid. +====================================================================================================== */ + + throwIfNotYMDDashDate(input, reason) { + + this.throwIfBlankOrNotString(input, reason); + + const parts = input.trim().split("-"); + + if (parts.length !== 3) { + this.throwCustomError("Date must be in format YYYY-MM-DD" + this._reasonMsg(reason)); + }; + + let [year, month, day] = parts; + + // --- YEAR --- + year = +year; + if (!Number.isInteger(year) || year < 1990 || year > 2100) { + this.throwCustomError("Year must be between 1990 and 2100" + this._reasonMsg(reason)); + }; + + const monthNum = +month; + if (month.length !== 2 || Number.isNaN(monthNum) || monthNum < 1 || monthNum > 12 ) { + this.throwCustomError(`Month must be between 01 and 12. Got: '${month}'` + this._reasonMsg(reason)); + }; + + const dayNum = +day; + if (day.length !== 2 || Number.isNaN(dayNum) || dayNum < 1 || dayNum > 31 ) { + this.throwCustomError(`Day must be between 01 and 31. Got: '${day}'` + this._reasonMsg(reason)); + }; + + }, + + +/* =================================================================================================== + *********** !!!!!! IMPORTANT !!!!!!!! ************************** + Objects in Pipedream require special treatment. Technically, they can be arrays + or even JSON strings. + + This function checks whether the input is a JS object, JS array, or a JSON string. + If it's a JSON string, it leaves it as-is but logs a message to the console indicating that a JSON string was received. + + It's up to the maintainer or contributor to decide what to do next. +//====================================================================================================*/ + + checkObject (input, reason){ + + // If type is string but can not be parsed as JSON then throw. + if (typeof input === "string") { + this.throwIfNotJSON(input, reason); + console.log ('Object was received as JSON.' + this._reasonMsg(reason)); + return; // Return early + }; + + // If it’s not a string, ensure it’s either an object or an array – otherwise, throw. + this.throwIfNotObjectOrArray(input, reason); + + }, + + +/* =================================================================================================== + Validates a URL string: + - Rejects blank strings, spaces, tabs, and newlines + - Warns about suspicious or unusual characters + - Adds a warning if protocol is missing or malformed + Returns: + { + warnings: string[], + url: string (trimmed original input) + } +====================================================================================================== */ + + // ===================================================================== + checkIfUrlValid(input, reason) { + + // Throws an error if the input is not a string or if its a blank string; + this.throwIfBlankOrNotString(input); + + // Warnin accumulator + let warnings = []; +; + // Trim the input (already checked for string); + const trimmedInput = input.trim(); + + // Reject if spaces, tabs, or newlines are present + if ((/[ \t\n]/.test(trimmedInput))) { + this.throwCustomError( `Url contains invalid characters like space, backslash etc., please check.` + this._reasonMsg(reason)) + }; + + // Warn about suspicious characters + const dubiousCharRegex = /[\\<>{}|"`^]|[\u200B\u200C\u200D\u2060\uFEFF\u00A0]/g; + + const dubiousMatches = trimmedInput.match(dubiousCharRegex); + + if (dubiousMatches) { + const uniqueChars = [...new Set(dubiousMatches)].join(" "); + + warnings.push(` URL contains dubious or non-standard characters " ${uniqueChars} " ` + + `that may be rejected by Google. Proceed only if you know what you are doing. ${this._reasonMsg(reason)}`) ; + }; + + // urlObject for further use if the next check passes. + let urlObject; + // Tries to create a new URL object with the input string; + try { + urlObject = new URL(trimmedInput); // throws if invalid or has no protocol + + // Warn if user typed only one slash (e.g., https:/) + if (/^(https?):\/(?!\/)/.test(input)) { + + warnings.push(` It looks like you're missing one slash after "${urlObject.protocol}".` + + `Did you mean "${urlObject.protocol}//..."? ${this._reasonMsg(reason)} `); + + }; + + } catch (err) { + // If the URL is invalid, try to create a new URL object with "http://" in case user forgot to add it; + try { + // If it works then there was no protocol in the input string; + urlObject = new URL("http://" + trimmedInput); + + warnings.push(` URL does not have http or https protocol "`); + + } catch(err) { + // If after all checks we are here that means that the url contain potentially unacceptable characters. + warnings.push(` URL contains potentionally unacceptable characters" ${this._reasonMsg(reason)}`); + + }; + + }; + + // Here I wanted to check for "ftp" and other protocols but if user uses them they know what they are doing. + + return warnings; + + }, + + /* =================================================================================================== + Validates a Site Identifier string, which may be either: + - A numeric Site ID (e.g., "123456") + - A custom or subdomain (e.g., "mysite.example.com") + + The function: + - Throws on blank or non-string input + - Detects and skips further checks if the input is a valid numeric Site ID + - If the input is not numeric, performs domain checks: + - Warns if the input includes protocol (http:// or https://) + - Warns if it starts with 'www.' + - Warns about unusual characters (only letters, numbers, dots, and dashes are allowed) + - Warns if the domain does not contain at least one dot (.) + + Returns: + string[]: An array of warning messages (empty if input is clean) +====================================================================================================== */ + + checkDomainOrId(input, reason) { + + const warnings = []; + + this.throwIfBlankOrNotString(input,reason); + const trimmed = this.trimIfString(input); + + + // Check if it’s a numeric ID (e.g. "123456") + const num = Number(trimmed); + const isNumericId = Number.isInteger(num) && num > 0; + + if (isNumericId) { + // Valid Site ID — skip domain checks + return warnings; + } + + // Now treat it as a domain and run checks: + if (/https?:\/\//.test(trimmed)) { + warnings.push(`Domain contains protocol (http or https). Remove it.` + this._reasonMsg(reason)); + } + + if (/^www\./i.test(trimmed)) { + warnings.push(`Domain starts with 'www.'. May or may not cause problems.` + this._reasonMsg(reason)); + } + + if (/[^a-zA-Z0-9.-]/.test(trimmed)) { + warnings.push(`Domain contains unusual characters. Only letters, numbers, dots, and dashes are allowed.` + this._reasonMsg(reason)); + } + + if (!trimmed.includes(".")) { + warnings.push(`Domain should contain at least one dot (e.g. example.com).` + this._reasonMsg(reason)); + } + + return warnings; + }, + + +/* =================================================================================================== + Appends a reason string to error messages for additional context. +====================================================================================================== */ + + _reasonMsg(reason){ + + return (reason && typeof reason === "string") ? ` Reason: ${reason} ` : ""; + }, + +}; diff --git a/components/wordpress_com/common/utils.mjs b/components/wordpress_com/common/utils.mjs new file mode 100644 index 0000000000000..0605663a91c86 --- /dev/null +++ b/components/wordpress_com/common/utils.mjs @@ -0,0 +1,24 @@ +// Returns a new object containing only standard prop fields, removing any custom keys. +// Considered using JSON deep cloning, but opted for a manual approach to safely preserve functions or complex types in future data structures. +export function removeCustomPropFields(input) { + const blacklist = new Set(["extendedType", "postBody"]); + const clean = {}; + + for (const key of Object.keys(input)) { + const prop = input[key]; + const cloned = {}; + + for (const field of Object.keys(prop)) { + if (!blacklist.has(field)) { + cloned[field] = prop[field]; + } + } + + clean[key] = cloned; + } + + return clean; + } + + + diff --git a/components/wordpress_com/package.json b/components/wordpress_com/package.json index 0d41d964ec38f..1f460f3478f6a 100644 --- a/components/wordpress_com/package.json +++ b/components/wordpress_com/package.json @@ -13,6 +13,8 @@ "access": "public" }, "dependencies": { - "@pipedream/platform": "^3.0.0" + "@pipedream/platform": "^3.0.0", + "http": "^0.0.1-security", + "open": "^10.1.1" } } diff --git a/components/wordpress_com/tests/README.md b/components/wordpress_com/tests/README.md new file mode 100644 index 0000000000000..d234532b520e0 --- /dev/null +++ b/components/wordpress_com/tests/README.md @@ -0,0 +1,44 @@ +# Local Tests Directory + +This folder contains **non-production test scripts** used for developing and validating Pipedream components locally. +**You can safely delete the entire folder** — it does not affect runtime or deployment. + +--- + +## Folder Structure + +### `action-tests/` +Component-level test runners that simulate a real Pipedream `$` context using `mockery-dollar.mjs`. + +- `test-retrieve-site-performance-data.mjs` – Tests the Search Analytics action +- `test-submit-url-for-indexing.mjs` – Tests the Indexing API action + +--- + +### `methods/` +Unit tests for reusable validation and utility methods found in `.app.mjs`. + +- `test-checkIfUrlValid.mjs` – Tests URL validation helper +- `test-throwIfNotYMDDashDate.mjs` – Tests strict date validation + +#### `bogus-data/` +Mocked data used to simulate edge-case user inputs and trigger validations: +- `bogus-data-url.mjs` – Invalid or suspicious URLs +- `bogus-data-google-date.mjs` – Date values to test against expected format + +--- + +### Root-level Utilities + +- `mockery-dollar.mjs` – Mocks the `$` object Pipedream injects into actions +- `get-token.mjs` – Script for manually supplying a Google OAuth token during local testing + +--- + +## ⚠️ Notes + +- Some files may contain **hardcoded tokens** — be sure to exclude them from commits. +- All files here are meant for **local testing only**. +- Delete this folder any time before publishing — it's safe and has no impact on your app. + +--- \ No newline at end of file diff --git a/components/wordpress_com/tests/action-tests/worpress_com-create-post.mjs b/components/wordpress_com/tests/action-tests/worpress_com-create-post.mjs new file mode 100644 index 0000000000000..9efb7e5c5f7a7 --- /dev/null +++ b/components/wordpress_com/tests/action-tests/worpress_com-create-post.mjs @@ -0,0 +1,135 @@ +/** + * ───────────────────────────────────────────────────────────────────────────── + * LOCAL TEST RUNNER – DO NOT DEPLOY + * + * This file is used to run and debug a Pipedream action **locally** outside + * of the Pipedream platform. Safe to delete. It does **not** affect production. + * + * It: + * - Injects mocked `$` object (logs, summary) + * - Bypasses OAuth by using hardcoded access token (get-token.mjs) + * - Validates, builds, and sends the Search Console request + * + * You MUST: + * - Replace `Authorization` token with a valid one manually + * - Ensure `siteUrl` is verified in your Search Console + * + * ───────────────────────────────────────────────────────────────────────────── + */ + +import mockery$ from "../mockery-dollar.mjs"; +import { axios } from "@pipedream/platform"; +import {removeCustomPropFields} from "../../common/utils.mjs"; +import wpress from "../../wordpress_com.app.mjs"; + + + +// TEST (FIX IN PRODUCTION) Remove completely +const mockeryData = { + site: "testsit38.wordpress.com", + title : "New Post", + content : "
HELLO WORLD
", + status : "draft", +}; + + +// Define prop metadata separately and spread it into the props object. +// Useful for accessing extended metadata during runtime — available because it stays in closure. + +const propsMeta = { + + site: { + type: "string", + label: "Site ID or domain", + extendedType : "domainOrId", + description: "Enter a site ID or domain (e.g. testsit38.wordpress.com). Do not include 'https://' or 'www'." + }, + title: { + type: "string", + label: "Post Title", + description: "The title of the post.", + postBody : true, + }, + content: { + type: "string", + label: "Post Content", + description: "The content of the post (HTML or text).", + postBody : true, + }, + status: { + type: "string", + label: "Status", + description: "The status of the post.", + options: [ + "publish", + "draft", + "private", + "pending", + ], + default: "draft", + postBody : true, + }, +}; + + + +//TEST (FIX IN PRODUCTION). Replace to export default. +const testAction = { + + ...mockeryData, + key: "worpress_com-create-post", + name: "Create New Post", + description: "Retrieves the authenticated user's account information.", + version: "0.0.1", + type: "action", + props: { + wpress, + // Remove custom prop metadata and spread only valid prop fields + ...removeCustomPropFields(propsMeta), + }, + + async run({ $ }) { + + + /*Validate props (check if they are of a valid data type). + Returns an array with warnings. + If there are no warnings, it returns an empty array.*/ + const warnings = wpress.methods.validationLoop(propsMeta, this); // TEST (FIX IN PRODUCTION) replace wpress.methods for this.wpress + + // Returns an object with props that has postBody === true + const payload = wpress.methods.preparePayload(propsMeta, this); // TEST (FIX IN PRODUCTION) replace wpress.methods for this.wpress + + let response; + try { + response = await axios($, { + method: "POST", + url : `https://public-api.wordpress.com/rest/v1.1/sites/${this.site}/posts/new`, + headers: { + Authorization: `Bearer v6Dp%5xWzh(rsgW9PR&xY4C#Ibs&Jo3W12nv@6Te3cZOrI5XACdoate$DQ#pIyYP`, + }, + data : payload, + }); + + $.export("$summary", `Post "${response.title}" created (ID: ${response.ID})` + "\n- " + warnings.join("\n- ")); + return response; + } catch (error) { + + const thrower = wpress.methods.checkWhoThrewError(error); //TEST + + throw new Error(`Failed to fetch data ( ${thrower.whoThrew} error ) : ${error.message}. ` + "\n- " + warnings.join("\n- ")); + + } + }, +}; + + + +//TEST (FIX IN PRODUCTION) +// await is just in case if node wants to finish its job before time =) +async function runTest() { + await testAction.run(mockery$); +} + +runTest(); + + diff --git a/components/wordpress_com/tests/get-token.mjs b/components/wordpress_com/tests/get-token.mjs new file mode 100644 index 0000000000000..7ff2a123c4272 --- /dev/null +++ b/components/wordpress_com/tests/get-token.mjs @@ -0,0 +1,80 @@ +// Bare-bones OAuth script to fetch a token. +// For testing purposes only — no timeouts, no browser response. +// Manually terminate the script from the console when finished. +// Access token v6Dp%5xWzh(rsgW9PR&xY4C#Ibs&Jo3W12nv@6Te3cZOrI5XACdoate$DQ#pIyYP +import open from "open"; +import http from "http"; + +// --- Google OAuth Config --- +const clientId = "116795"; +const redirectUri = "http://localhost:3000"; +const scopes = "global"; + +const authUrl = new URL("https://public-api.wordpress.com/oauth2/authorize"); +authUrl.searchParams.set("client_id", clientId); +authUrl.searchParams.set("redirect_uri", redirectUri); +authUrl.searchParams.set("response_type", "code"); +authUrl.searchParams.set("scope", scopes); +authUrl.searchParams.set("access_type", "offline"); +authUrl.searchParams.set("prompt", "consent"); + + +new Promise((resolve, reject) => { + // Start a local HTTP server to catch Google's OAuth redirect + http.createServer((incomingRequest, outgoingResponse) => { + const fullRequestUrl = new URL(incomingRequest.url, `http://${incomingRequest.headers.host}`); + const authorizationCode = fullRequestUrl.searchParams.get("code"); + + if (authorizationCode) { + console.log("AUTH CODE RECEIVED:", authorizationCode); + resolve(authorizationCode); + } else { + console.log("Unexpected request received — no code param found."); + reject("Unexpected request received — no code param."); + } + }).listen(3000, () => { + console.log("🌐 Listening on http://localhost:3000"); + console.log("🔗 Opening browser for Google OAuth..."); + open(authUrl.toString()); + }); +}) +.then((authorizationCode) => { + console.log("✅ Got the code! Exchanging for tokens..."); + + + fetch("https://public-api.wordpress.com/oauth2/token", { + method: "POST", + headers: { + "Content-Type": "application/x-www-form-urlencoded", + }, + body: new URLSearchParams({ + code: authorizationCode, + client_id: clientId, + client_secret: "E7BSLxmS3u572GBn3wZ82LTLTGs3VIzWxLtim2yuVI0wKYdH8Y7cXTRXtO7zj5yK", + redirect_uri: redirectUri, + grant_type: "authorization_code", + }), + }) + .then((res) => res.json()) + .then((data) => { + if (data.access_token) { + console.log("🎉 Tokens received!"); + console.log("Access Token:", data.access_token); + console.log("Refresh Token:", data.refresh_token); + console.log("Expires In:", data.expires_in); + console.log("Token retrieval complete. Shutting down server..."); + process.exit(0); + } else { + console.error(" Failed to get tokens:", data); + process.exit(1); + } + }) + .catch((err) => { + console.error("Error while exchanging code:", err); + process.exit(1); + }); + }) + .catch((error) => { + console.error("Error during authorization:", error); + process.exit(1); + }); \ No newline at end of file diff --git a/components/wordpress_com/tests/methods/bogus-data/bogus-data-google-date.mjs b/components/wordpress_com/tests/methods/bogus-data/bogus-data-google-date.mjs new file mode 100644 index 0000000000000..5cef1f45fcaca --- /dev/null +++ b/components/wordpress_com/tests/methods/bogus-data/bogus-data-google-date.mjs @@ -0,0 +1,113 @@ +export default { + aSingleWordString : { + value: "wrgwgwepkgprgprpwnwwrehwrh", + jsType : "string", + extendedType : "singleWordString", + }, + aMultiWordString : { + value: "etheth etphmpethm ethpethm", + jsType : "string", + extendedType : "aMultiWordString", + }, + aPositiveInteger : { + value:15115, + jsType: "number", + extendedType : "positiveInteger", + } , + aNegativeInteger : { + value:-15115, + jsType: "number", + extendedType : "negativeInteger", + } , + aPositiveFloat : { + value:12.155, + jsType: "number", + extendedType : "positiveFloat", + } , + aNegativeFloat : { + value:-12.155, + jsType: "number", + extendedType : "negativeFloat", + } , + aZero : { + value:0, + jsType: "number", + extendedType : "zero", + } , + anArray : { + value : [13135, 35.15, "3feqe", [1,2,3], {some:"val"}, 0], + jsType : "object", + extendedType : "trueArray" + }, + + anArrayOfStrings : { + value : ["string1", "string2", "string3"], + jsType : "object", + extendedType : "arrayOfStrings" + }, + anObject : { + value : {some: "val", some2: 23, some3 : [1,2,3]}, + jsType : "object", + extendedType : "trueObject" + }, + + anEmptyString : { + value : "", + jsType : "string", + extendedType : "emptyString" + }, + + aSpace : { + value : " ", + jsType : "string", + extendedType : "space" + }, + + + aValidYMDDashDate : { + value : `2025-05-28`, + jsType : "string", + extendedType : "ymdDashDate" + }, + + + aCharinYMDDashDate : { + value : `20G5-h5-28`, + jsType : "string", + extendedType : "ymdDashDate" + }, + + aShortYearYMDDashDate : { + value : `20-23-28`, + jsType : "string", + extendedType : "ymdDashDate" + }, + + aMonthOusideValYMDDashDate : { + value : `2025-23-28`, + jsType : "string", + extendedType : "ymdDashDate" + }, + + aDayOusideValYMDDashDate : { + value : `2025-12-56`, + jsType : "string", + extendedType : "ymdDashDate" + }, + + anInvalidSeparatorYMDDashDate : { + value : `2025/12.56`, + jsType : "string", + extendedType : "ymdDashDate" + }, + + + + + + +}; + + + + \ No newline at end of file diff --git a/components/wordpress_com/tests/methods/bogus-data/bogus-data-url.mjs b/components/wordpress_com/tests/methods/bogus-data/bogus-data-url.mjs new file mode 100644 index 0000000000000..b64f0be5e0ebb --- /dev/null +++ b/components/wordpress_com/tests/methods/bogus-data/bogus-data-url.mjs @@ -0,0 +1,111 @@ +export default { + aSingleWordString : { + value: "wrgwgwepkgprgprpwnwwrehwrh", + jsType : "string", + extendedType : "singleWordString", + }, + aMultiWordString : { + value: "etheth etphmpethm ethpethm", + jsType : "string", + extendedType : "aMultiWordString", + }, + aPositiveInteger : { + value:15115, + jsType: "number", + extendedType : "positiveInteger", + } , + aNegativeInteger : { + value:-15115, + jsType: "number", + extendedType : "negativeInteger", + } , + aPositiveFloat : { + value:12.155, + jsType: "number", + extendedType : "positiveFloat", + } , + aNegativeFloat : { + value:-12.155, + jsType: "number", + extendedType : "negativeFloat", + } , + aZero : { + value:0, + jsType: "number", + extendedType : "zero", + } , + anArray : { + value : [13135, 35.15, "3feqe", [1,2,3], {some:"val"}, 0], + jsType : "object", + extendedType : "trueArray" + }, + + anArrayOfStrings : { + value : ["string1", "string2", "string3"], + jsType : "object", + extendedType : "arrayOfStrings" + }, + anObject : { + value : {some: "val", some2: 23, some3 : [1,2,3]}, + jsType : "object", + extendedType : "trueObject" + }, + + anEmptyString : { + value : "", + jsType : "string", + extendedType : "emptyString" + }, + + aSpace : { + value : " ", + jsType : "string", + extendedType : "space" + }, + + aValidHttpUrl : { + value : "http://example.com", + jsType : "string", + extendedType : "url" + }, + + aValidHttpsUrl : { + value : "https://example.com", + jsType : "string", + extendedType : "url" + }, + + aValidUrlWithoutProtocol : { + value : "example.com", + jsType : "string", + extendedType : "url" + }, + anUrlWithSpace : { + value : "https://exa mple.com", + jsType : "string", + extendedType : "url" + }, + + aReverseSlashUrl : { + value : "http://example.com\\path", + jsType : "string", + extendedType : "url" + }, + + aOneSlashUrl : { + value : "http:/example.com", + jsType : "string", + extendedType : "url" + }, + + aValidUrlWithDubiousCharacters : { + value : `http://example.com\\