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
9 changes: 5 additions & 4 deletions actions/setup/js/update_project.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -1227,10 +1227,11 @@ async function main(config = {}, githubClient = null) {
const projectStr = effectiveProjectUrl.trim();
const projectWithoutHash = projectStr.startsWith("#") ? projectStr.substring(1) : projectStr;

// Check if it's a temporary ID (aw_XXXXXXXXXXXX)
if (/^aw_[0-9a-f]{12}$/i.test(projectWithoutHash)) {
// Look up in the unified temporaryIdMap
const resolved = tempIdMap.get(projectWithoutHash.toLowerCase());
// Check if it's a temporary ID using the canonical pattern (aw_XXX to aw_XXXXXXXX)
if (isTemporaryId(projectWithoutHash)) {
// Look up in the unified temporaryIdMap using normalized (lowercase) ID
const normalizedId = normalizeTemporaryId(projectWithoutHash);
const resolved = tempIdMap.get(normalizedId);
if (resolved && typeof resolved === "object" && "projectUrl" in resolved && resolved.projectUrl) {
core.info(`Resolved temporary project ID ${projectStr} to ${resolved.projectUrl}`);
effectiveProjectUrl = resolved.projectUrl;
Expand Down
142 changes: 142 additions & 0 deletions actions/setup/js/update_project.test.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -1795,3 +1795,145 @@ describe("updateProject", () => {
expect(result.error).toContain("transient GitHub API error");
});
});

describe("update_project temporary project ID resolution", () => {
let mockSetup;
let messageHandler;

beforeEach(() => {
vi.clearAllMocks();

// Reset mock implementation
mockGithub.graphql.mockReset();

// Create a minimal mock setup for the handler
mockSetup = {
core: mockCore,
github: mockGithub,
context: mockContext,
updateProjectHandlerFactory,
};
});

afterEach(() => {
vi.clearAllMocks();
});

it("resolves temporary project ID with 8 alphanumeric characters (generated format)", async () => {
const temporaryId = "aw_AbC12345"; // 8 chars, mixed case
const projectUrl = "https://github.com/orgs/testowner/projects/99";
const tempIdMap = new Map();
tempIdMap.set("aw_abc12345", { projectUrl }); // Stored in lowercase

queueResponses([repoResponse(), viewerResponse(), orgProjectV2Response(projectUrl, 99, "project-resolved"), issueResponse("issue-id-1"), existingItemResponse("issue-id-1", "item-resolved"), fieldsResponse([])]);

// Create handler with config
const config = { max: 100 };
messageHandler = await updateProjectHandlerFactory(config);

const message = {
type: "update_project",
project: temporaryId, // Using temporary ID
content_type: "issue",
content_number: 42,
};

const result = await messageHandler(message, {}, tempIdMap);

expect(result.success).toBe(true);
expect(mockCore.info).toHaveBeenCalledWith(`Resolved temporary project ID ${temporaryId} to ${projectUrl}`);
});

it("resolves temporary project ID with # prefix", async () => {
const temporaryId = "#aw_Test99"; // With hash prefix
const projectUrl = "https://github.com/orgs/testowner/projects/88";
const tempIdMap = new Map();
tempIdMap.set("aw_test99", { projectUrl }); // Stored without hash, lowercase

queueResponses([repoResponse(), viewerResponse(), orgProjectV2Response(projectUrl, 88, "project-hash"), issueResponse("issue-id-2"), existingItemResponse("issue-id-2", "item-hash"), fieldsResponse([])]);

const config = { max: 100 };
messageHandler = await updateProjectHandlerFactory(config);

const message = {
type: "update_project",
project: temporaryId,
content_type: "issue",
content_number: 43,
};

const result = await messageHandler(message, {}, tempIdMap);

expect(result.success).toBe(true);
expect(mockCore.info).toHaveBeenCalledWith(`Resolved temporary project ID ${temporaryId} to ${projectUrl}`);
});

it("resolves temporary project ID with 3 characters (minimum)", async () => {
const temporaryId = "aw_abc"; // 3 chars minimum
const projectUrl = "https://github.com/orgs/testowner/projects/77";
const tempIdMap = new Map();
tempIdMap.set("aw_abc", { projectUrl });

queueResponses([repoResponse(), viewerResponse(), orgProjectV2Response(projectUrl, 77, "project-min"), issueResponse("issue-id-3"), existingItemResponse("issue-id-3", "item-min"), fieldsResponse([])]);

const config = { max: 100 };
messageHandler = await updateProjectHandlerFactory(config);

const message = {
type: "update_project",
project: temporaryId,
content_type: "issue",
content_number: 44,
};

const result = await messageHandler(message, {}, tempIdMap);

expect(result.success).toBe(true);
expect(mockCore.info).toHaveBeenCalledWith(`Resolved temporary project ID ${temporaryId} to ${projectUrl}`);
});

it("throws error when temporary project ID is not found in map", async () => {
const temporaryId = "aw_NotFound";
const tempIdMap = new Map(); // Empty map

const config = { max: 100 };
messageHandler = await updateProjectHandlerFactory(config);

const message = {
type: "update_project",
project: temporaryId,
content_type: "issue",
content_number: 45,
};

const result = await messageHandler(message, {}, tempIdMap);

expect(result.success).toBe(false);
expect(result.error).toMatch(/Temporary project ID 'aw_NotFound' not found.*Ensure create_project was called before update_project/);
});

it("handles full project URL normally (not treated as temporary ID)", async () => {
const projectUrl = "https://github.com/orgs/testowner/projects/66";
const tempIdMap = new Map();
// Map has an entry, but it shouldn't be used since we're passing full URL
tempIdMap.set("aw_other", { projectUrl: "https://github.com/orgs/other/projects/1" });

queueResponses([repoResponse(), viewerResponse(), orgProjectV2Response(projectUrl, 66, "project-full"), issueResponse("issue-id-4"), existingItemResponse("issue-id-4", "item-full"), fieldsResponse([])]);

const config = { max: 100 };
messageHandler = await updateProjectHandlerFactory(config);

const message = {
type: "update_project",
project: projectUrl, // Full URL, not temporary ID
content_type: "issue",
content_number: 46,
};

const result = await messageHandler(message, {}, tempIdMap);

expect(result.success).toBe(true);
// Should NOT log temporary ID resolution
expect(mockCore.info).not.toHaveBeenCalledWith(expect.stringContaining("Resolved temporary project ID"));
});
});
Loading