Drop manifest script tests — graph_cloud validation is two inline checks
This commit is contained in:
@@ -20,17 +20,6 @@ env:
|
||||
CLAUDE_VERSION: 2.1.143
|
||||
|
||||
jobs:
|
||||
# build-manifest.mjs validates manifest URL params (incl. the sovereign-cloud
|
||||
# graph_cloud enum) — keep its tests green alongside plugin linting.
|
||||
manifest-script-tests:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 22
|
||||
- run: node --test claude-for-msft-365-install/scripts/build-manifest.test.mjs
|
||||
|
||||
validate:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
|
||||
@@ -6,7 +6,6 @@
|
||||
// Example: node build-manifest.mjs office acme.xml gcp_project_id=acme gcp_region=us-east5
|
||||
|
||||
import { writeFileSync } from "node:fs";
|
||||
import { fileURLToPath } from "node:url";
|
||||
|
||||
const MANIFESTS = {
|
||||
office: "https://pivot.claude.ai/manifest.xml", // Excel + Word + PowerPoint (TaskPaneApp)
|
||||
@@ -69,8 +68,8 @@ const KEYS = {
|
||||
hint: "scope(s) for your Entra-protected API, e.g. api://<your-app-guid>/.default — comma/space-separated list allowed, requires graph_client_id",
|
||||
},
|
||||
graph_cloud: {
|
||||
// Shape-checked here like every key; the real (throwing) validation is in
|
||||
// validateGraphCloud below — this steers where OAuth tokens go.
|
||||
// The add-in rejects unrecognized values at load and falls back to global,
|
||||
// so a typo here degrades to commercial endpoints — heed the warning.
|
||||
pattern: /^(global|us-gov-high|us-gov-dod|china)$/,
|
||||
hint: "Microsoft national cloud: global | us-gov-high | us-gov-dod | china — only for sovereign clouds; GCC-High and 21Vianet are also auto-detected at sign-in",
|
||||
},
|
||||
@@ -86,39 +85,24 @@ const KEYS = {
|
||||
|
||||
const NEEDS_ENTRA = ["aws_role_arn", "graph_client_id", "entra_scope"];
|
||||
|
||||
// --- Sovereign / national clouds ---------------------------------------------
|
||||
//
|
||||
// graph_cloud steers where the add-in sends the user's OAuth tokens, so
|
||||
// unlike other keys a bad value is a hard error, not a shape warning. The
|
||||
// config surface is an enum value, never a URL — each value maps to a fixed
|
||||
// Graph + Entra endpoint pair inside the add-in (src/auth/graphCloud.ts in
|
||||
// the add-in repo), so there is no admin-supplied hostname to validate.
|
||||
// These checks mirror what the add-in enforces at load so admins see the
|
||||
// failure here instead of as an opaque AADSTS error after deploying.
|
||||
|
||||
const GRAPH_CLOUDS = ["global", "us-gov-high", "us-gov-dod", "china"];
|
||||
|
||||
function validateGraphCloud(params) {
|
||||
const cloud = params.get("graph_cloud");
|
||||
if (!cloud) return;
|
||||
if (!GRAPH_CLOUDS.includes(cloud)) {
|
||||
throw new Error(`graph_cloud "${cloud}" is not a recognized value (expected one of: ${GRAPH_CLOUDS.join(", ")})`);
|
||||
async function main() {
|
||||
const [host, out, ...pairs] = process.argv.slice(2);
|
||||
const manifestUrl = process.env.MANIFEST_URL || MANIFESTS[host];
|
||||
if (!manifestUrl || !out || pairs.length === 0) {
|
||||
console.error("Usage: node build-manifest.mjs <office|outlook> <out.xml> key=value [key=value ...]");
|
||||
console.error(`Keys: ${Object.keys(KEYS).join(", ")}`);
|
||||
process.exit(1);
|
||||
}
|
||||
// A non-global cloud needs a BYO Entra app — Anthropic's multi-tenant app
|
||||
// exists only in the commercial cloud, so the default client_id against a
|
||||
// sovereign authority fails with an opaque AADSTS700016 at sign-in.
|
||||
if (cloud !== "global" && !params.has("graph_client_id")) {
|
||||
throw new Error(
|
||||
`graph_cloud=${cloud} requires a graph_client_id registered in that cloud — ` +
|
||||
`Anthropic's multi-tenant application exists only in the global cloud`,
|
||||
);
|
||||
if (host === "outlook" && pairs.some((p) => p.startsWith("aws_"))) {
|
||||
console.error("error: Amazon Bedrock (aws_role_arn/aws_region) is not currently supported for Outlook");
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
if (host !== "outlook" && pairs.some((p) => p.startsWith("graph_client_id="))) {
|
||||
console.warn("note: graph_client_id only applies to Outlook; it has no effect in the office manifest");
|
||||
}
|
||||
// graph_cloud is relevant to both manifests: in Outlook it steers Graph +
|
||||
// Entra sign-in; in office it steers the Entra SSO authority.
|
||||
|
||||
/** Validate key=value pairs and return the URLSearchParams that get appended
|
||||
* to the manifest's taskpane URL. Throws on anything the add-in would reject
|
||||
* at load; shape mismatches only warn. */
|
||||
export function buildParams(pairs) {
|
||||
const params = new URLSearchParams();
|
||||
for (const p of pairs) {
|
||||
const eq = p.indexOf("=");
|
||||
@@ -144,29 +128,13 @@ export function buildParams(pairs) {
|
||||
if (params.has("entra_scope") && !params.has("graph_client_id")) {
|
||||
throw new Error("entra_scope requires graph_client_id (the scope is requested as your own Entra app, not the default)");
|
||||
}
|
||||
validateGraphCloud(params);
|
||||
return params;
|
||||
}
|
||||
|
||||
async function main() {
|
||||
const [host, out, ...pairs] = process.argv.slice(2);
|
||||
const manifestUrl = process.env.MANIFEST_URL || MANIFESTS[host];
|
||||
if (!manifestUrl || !out || pairs.length === 0) {
|
||||
console.error("Usage: node build-manifest.mjs <office|outlook> <out.xml> key=value [key=value ...]");
|
||||
console.error(`Keys: ${Object.keys(KEYS).join(", ")}`);
|
||||
process.exit(1);
|
||||
// A non-global graph_cloud needs a BYO Entra app — Anthropic's multi-tenant
|
||||
// app exists only in the commercial cloud, so the default client_id against
|
||||
// a sovereign authority fails with an opaque AADSTS700016 at sign-in.
|
||||
const cloud = params.get("graph_cloud");
|
||||
if (cloud && cloud !== "global" && !params.has("graph_client_id")) {
|
||||
throw new Error(`graph_cloud=${cloud} requires a graph_client_id registered in that cloud`);
|
||||
}
|
||||
if (host === "outlook" && pairs.some((p) => p.startsWith("aws_"))) {
|
||||
console.error("error: Amazon Bedrock (aws_role_arn/aws_region) is not currently supported for Outlook");
|
||||
process.exit(1);
|
||||
}
|
||||
if (host !== "outlook" && pairs.some((p) => p.startsWith("graph_client_id="))) {
|
||||
console.warn("note: graph_client_id only applies to Outlook; it has no effect in the office manifest");
|
||||
}
|
||||
// graph_cloud is relevant to both manifests: in Outlook it steers Graph +
|
||||
// Entra sign-in; in office it steers the Entra SSO authority.
|
||||
|
||||
const params = buildParams(pairs);
|
||||
|
||||
// URLSearchParams joins with `&`; XML attribute values need it escaped.
|
||||
const qs = params.toString().replaceAll("&", "&");
|
||||
@@ -187,9 +155,7 @@ async function main() {
|
||||
console.log(`Wrote ${out} (${host}, params: ${params})`);
|
||||
}
|
||||
|
||||
if (process.argv[1] && fileURLToPath(import.meta.url) === process.argv[1]) {
|
||||
main().catch((err) => {
|
||||
console.error(err.message || err);
|
||||
process.exit(1);
|
||||
});
|
||||
}
|
||||
main().catch((err) => {
|
||||
console.error(err.message || err);
|
||||
process.exit(1);
|
||||
});
|
||||
|
||||
@@ -1,49 +0,0 @@
|
||||
// Run with: node --test claude-for-msft-365-install/scripts/build-manifest.test.mjs
|
||||
import assert from "node:assert/strict";
|
||||
import { test } from "node:test";
|
||||
|
||||
import { buildParams } from "./build-manifest.mjs";
|
||||
|
||||
const GUID = "11111111-2222-3333-4444-555555555555";
|
||||
|
||||
test("no sovereign params → query string unchanged from before", () => {
|
||||
const params = buildParams(["gcp_project_id=acme-prod", "gcp_region=us-east5", "auto_connect=0"]);
|
||||
assert.equal(params.toString(), "gcp_project_id=acme-prod&gcp_region=us-east5&auto_connect=0");
|
||||
});
|
||||
|
||||
test("GCC-High needs only graph_client_id — the cloud is auto-detected at sign-in", () => {
|
||||
const params = buildParams([`graph_client_id=${GUID}`, "entra_sso=1"]);
|
||||
assert.equal(params.get("graph_client_id"), GUID);
|
||||
assert.equal(params.get("graph_cloud"), null);
|
||||
});
|
||||
|
||||
test("each cloud value passes with graph_client_id", () => {
|
||||
for (const cloud of ["us-gov-high", "us-gov-dod", "china"]) {
|
||||
const params = buildParams([`graph_client_id=${GUID}`, "entra_sso=1", `graph_cloud=${cloud}`]);
|
||||
assert.equal(params.get("graph_cloud"), cloud);
|
||||
}
|
||||
});
|
||||
|
||||
test("graph_cloud=global passes without graph_client_id", () => {
|
||||
const params = buildParams(["graph_cloud=global"]);
|
||||
assert.equal(params.get("graph_cloud"), "global");
|
||||
});
|
||||
|
||||
test("non-global cloud without graph_client_id fails with a clear message", () => {
|
||||
assert.throws(() => buildParams(["graph_cloud=us-gov-dod"]), /requires a graph_client_id/);
|
||||
});
|
||||
|
||||
test("unrecognized cloud values fail — URLs are not accepted", () => {
|
||||
for (const bad of ["us-gov", "GCC-High", "https://graph.microsoft.us", "dod"]) {
|
||||
assert.throws(
|
||||
() => buildParams([`graph_client_id=${GUID}`, "entra_sso=1", `graph_cloud=${bad}`]),
|
||||
/not a recognized value/,
|
||||
bad,
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
test("removed URL params are rejected as unknown keys", () => {
|
||||
assert.throws(() => buildParams(["graph_endpoint=https://dod-graph.microsoft.us"]), /unknown key/);
|
||||
assert.throws(() => buildParams(["graph_auth_endpoint=https://login.microsoftonline.us"]), /unknown key/);
|
||||
});
|
||||
Reference in New Issue
Block a user