Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 26 additions & 5 deletions api/lib/get-fields-update-query.js
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ export function getFieldsUpdateQuery(state, fields) {
value === null
? ""
: "optionsByValue" in field
? findFieldOptionId(state, field.optionsByValue, value)
? findFieldOptionId(state, field, value)
: value;

const queryNodes =
Expand All @@ -84,20 +84,41 @@ export function getFieldsUpdateQuery(state, fields) {
}

function escapeQuotes(str) {
// TODO: add test for when `str` is not a "string"
/* c8 ignore next */
return typeof str === "string" ? str.replace(/\"/g, '\\"') : str;
}

/**
* @param {import("../..").GitHubProjectStateWithFields | import("../..").GitHubProjectStateWithItems} state
* @param {Record<string, string>} optionsByValue
* @param {import("../..").ProjectFieldWithOptions} field
* @param {string} value
*
* @returns {string | undefined}
* @returns {string}
*/
function findFieldOptionId(state, optionsByValue, value) {
function findFieldOptionId(state, field, value) {
const [_optionValue, optionId] =
Object.entries(optionsByValue).find(([optionValue]) =>
Object.entries(field.optionsByValue).find(([optionValue]) =>
state.matchFieldOptionValue(optionValue, value.trim())
) || [];

if (!optionId) {
const knownOptions = Object.keys(field.optionsByValue);
const existingOptionsString = knownOptions
.map((value) => `- ${value}`)
.join("\n");

throw Object.assign(
new Error(
`[github-project] "${value}" is an invalid option for "${field.name}".\n\nKnown options are:\n${existingOptionsString}`
),
{
code: "E_GITHUB_PROJECT_UNKNOWN_FIELD_OPTION",
knownOptions,
userOption: value,
}
);
}

return optionId;
}
76 changes: 68 additions & 8 deletions test.js
Original file line number Diff line number Diff line change
Expand Up @@ -1350,7 +1350,7 @@ test("project.items.update(itemNodeId, fields) not found", async (t) => {
});

const updatedItem = await project.items.update("<unknown id>", {
relevantToUsers: "yes",
relevantToUsers: "Yes",
});

t.deepEqual(updatedItem, undefined);
Expand Down Expand Up @@ -1415,7 +1415,7 @@ test("project.items.update(itemNodeId, fields) unforeseen GraphQL error", async

try {
await project.items.update("<unknown id>", {
relevantToUsers: "yes",
relevantToUsers: "Yes",
});
t.fail("Should have thrown");
} catch (error) {
Expand Down Expand Up @@ -1467,7 +1467,7 @@ test("project.items.update(itemNodeId, fields) with non GraphQL error", async (t

try {
await project.items.update("<unknown id>", {
relevantToUsers: "yes",
relevantToUsers: "Yes",
});
t.fail("should have thrown");
} catch (error) {
Expand Down Expand Up @@ -1700,6 +1700,66 @@ test("project.items.update(itemNodeId, fields) with custom status field", async
});
});

test("project.items.update(itemNodeId, fields) with invalid field option", async (t) => {
const { getProjectFieldsQueryResultFixture } = await import(
"./test/fixtures/get-project-fields/query-result.js"
);
const { issueItemFixture } = await import(
"./test/fixtures/get-item/issue-item.js"
);

const octokit = new Octokit();
octokit.hook.wrap("request", async (request, options) => {
t.deepEqual(options.method, "POST");
t.deepEqual(options.url, "/graphql");

if (/query getProjectCoreData\(/.test(options.query)) {
t.deepEqual(options.variables, {
org: "org",
number: 1,
});

return {
data: getProjectFieldsQueryResultFixture,
};
}

throw new Error(
`Unexpected query:\n${prettier.format(options.query, {
parser: "graphql",
})}`
);
});

const project = new GitHubProject({
org: "org",
number: 1,
octokit,
fields: {
status: "Relevant to users?",
},
});

try {
await project.items.update("PNI_lADOBYMIeM0lfM4ADfm9", {
status: "Unknown option",
});
t.fail("Should not resolve");
} catch (error) {
t.is(error.code, "E_GITHUB_PROJECT_UNKNOWN_FIELD_OPTION");
t.deepEqual(error.knownOptions, ["Yes", "No"]);
t.is(error.userOption, "Unknown option");
t.is(
error.message,
`[github-project] "Unknown option" is an invalid option for "Relevant to users?".

Known options are:
- Yes
- No`
);
}
});

test("project.items.updateByContentId(contentNodeId, fields)", async (t) => {
const { getProjectItemsQueryResultFixture } = await import(
"./test/fixtures/get-project-items/query-result.js"
Expand Down Expand Up @@ -1754,15 +1814,15 @@ test("project.items.updateByContentId(contentNodeId, fields)", async (t) => {
const updatedItem = await project.items.updateByContentId(
"I_kwDOGNkQys49IizC",
{
relevantToUsers: "yes",
relevantToUsers: "Yes",
}
);

t.deepEqual(updatedItem, {
...issueItemFixture,
fields: {
...issueItemFixture.fields,
relevantToUsers: "yes",
relevantToUsers: "Yes",
},
});
});
Expand Down Expand Up @@ -1816,7 +1876,7 @@ test("project.items.updateByContentId(contentNodeId, fields) not found", async (
});

const updatedItem = await project.items.updateByContentId("<unknown id>", {
relevantToUsers: "yes",
relevantToUsers: "Yes",
});

t.deepEqual(updatedItem, undefined);
Expand Down Expand Up @@ -1877,15 +1937,15 @@ test("project.items.updateByContentRepositoryAndNumber(contentNodeId, fields)",
"example-product",
2,
{
relevantToUsers: "yes",
relevantToUsers: "Yes",
}
);

t.deepEqual(updatedItem, {
...issueItemFixture,
fields: {
...issueItemFixture.fields,
relevantToUsers: "yes",
relevantToUsers: "Yes",
},
});
});
Expand Down