Skip to content

Commit

Permalink
fix(api-elasticsearch): encode HTML special chars for cursor (#1958)
Browse files Browse the repository at this point in the history
  • Loading branch information
brunozoric committed Oct 13, 2021
1 parent 55c6ab7 commit 292dc0a
Show file tree
Hide file tree
Showing 2 changed files with 60 additions and 13 deletions.
10 changes: 7 additions & 3 deletions packages/api-elasticsearch/src/cursors.ts
@@ -1,11 +1,13 @@
/**
* Encode a received cursor value into something that can be passed on to the user.
*/
export const encodeCursor = (cursor?: string): string | undefined => {
export const encodeCursor = (cursor?: string | string[]): string | undefined => {
if (!cursor) {
return undefined;
}

cursor = Array.isArray(cursor) ? cursor.map(encodeURIComponent) : encodeURIComponent(cursor);

try {
return Buffer.from(JSON.stringify(cursor)).toString("base64");
} catch (ex) {
Expand All @@ -17,12 +19,14 @@ export const encodeCursor = (cursor?: string): string | undefined => {
* Decode a received value into a Elasticsearch cursor.
* If no value is received or is not decodable, return undefined.
*/
export const decodeCursor = (cursor?: string): string | undefined => {
export const decodeCursor = (cursor?: string): string[] | string | undefined => {
if (!cursor) {
return undefined;
}
try {
return JSON.parse(Buffer.from(cursor, "base64").toString("ascii"));
const value = JSON.parse(Buffer.from(cursor, "base64").toString("ascii"));

return Array.isArray(value) ? value.map(decodeURIComponent) : decodeURIComponent(value);
} catch (ex) {
console.error(ex.message);
}
Expand Down
63 changes: 53 additions & 10 deletions packages/api-headless-cms/__tests__/contentAPI/sorting.test.ts
Expand Up @@ -4,7 +4,7 @@ import { setupContentModelGroup, setupContentModels } from "../utils/setup";
import { useFruitReadHandler } from "../utils/useFruitReadHandler";

const appleData = {
name: "Ap ` ple",
name: "A’p ` pl ' e",
isSomething: false,
rating: 400,
numbers: [5, 6, 7.2, 10.18, 12.05],
Expand All @@ -19,7 +19,7 @@ const appleData = {
};

const strawberryData = {
name: "Straw ` berry",
name: "Straw `er ' ry",
isSomething: true,
rating: 500,
numbers: [5, 6, 7.2, 10.18, 12.05],
Expand All @@ -34,7 +34,7 @@ const strawberryData = {
};

const bananaData = {
name: "Ban ` ana",
name: "Ban ` a 'na",
isSomething: false,
rating: 450,
numbers: [5, 6, 7.2, 10.18, 12.05],
Expand All @@ -48,6 +48,21 @@ const bananaData = {
time: "11:59:01"
};

const grahamData = {
name: "Graham O’Keeffe",
isSomething: false,
rating: 450,
numbers: [5, 6, 7.2, 10.18, 12.05],
email: "graham@doe.com",
url: "https://graham.test",
lowerCase: "graham",
upperCase: "GRAHAM",
date: "2020-12-03",
dateTime: new Date("2020-12-03T12:12:21").toISOString(),
dateTimeZ: "2020-12-03T14:52:41+01:00",
time: "11:59:01"
};

describe("sorting + cursor", () => {
const manageOpts = { path: "manage/en-US" };
const readOpts = { path: "read/en-US" };
Expand All @@ -65,6 +80,9 @@ describe("sorting + cursor", () => {
data
});

if (response.data.createFruit.error) {
throw new Error(response.data.createFruit.error.message);
}
const createdFruit = response.data.createFruit.data;

const [publish] = await publishFruit({
Expand All @@ -86,7 +104,8 @@ describe("sorting + cursor", () => {
return {
apple: await createAndPublishFruit(appleData),
strawberry: await createAndPublishFruit(strawberryData),
banana: await createAndPublishFruit(bananaData)
banana: await createAndPublishFruit(bananaData),
graham: await createAndPublishFruit(grahamData)
};
};

Expand All @@ -100,13 +119,13 @@ describe("sorting + cursor", () => {
// If this `until` resolves successfully, we know entry is accessible via the "read" API
await until(
() => listFruits({}).then(([data]) => data),
({ data }) => data.listFruits.data.length === 3,
({ data }) => data.listFruits.data.length === 4,
{ name: "list all fruits", tries: 10 }
);
};

test("should load items with after cursor with special characters", async () => {
const { apple, banana, strawberry } = await setupFruits();
const { apple, graham, banana, strawberry } = await setupFruits();

const handler = useFruitReadHandler({
...readOpts
Expand All @@ -130,7 +149,7 @@ describe("sorting + cursor", () => {
],
meta: {
hasMoreItems: true,
totalCount: 3,
totalCount: 4,
cursor: expect.any(String)
},
error: null
Expand All @@ -154,20 +173,44 @@ describe("sorting + cursor", () => {
],
meta: {
hasMoreItems: true,
totalCount: 3,
totalCount: 4,
cursor: expect.any(String)
},
error: null
}
}
});

const [strawberryListResponse] = await listFruits({
const [grahamListResponse] = await listFruits({
sort: ["name_ASC"],
limit: 1,
after: bananaListResponse.data.listFruits.meta.cursor
});

expect(grahamListResponse).toEqual({
data: {
listFruits: {
data: [
{
...graham
}
],
meta: {
hasMoreItems: true,
totalCount: 4,
cursor: expect.any(String)
},
error: null
}
}
});

const [strawberryListResponse] = await listFruits({
sort: ["name_ASC"],
limit: 1,
after: grahamListResponse.data.listFruits.meta.cursor
});

expect(strawberryListResponse).toEqual({
data: {
listFruits: {
Expand All @@ -178,7 +221,7 @@ describe("sorting + cursor", () => {
],
meta: {
hasMoreItems: false,
totalCount: 3,
totalCount: 4,
cursor: null
},
error: null
Expand Down

0 comments on commit 292dc0a

Please sign in to comment.