|
| 1 | +import * as fs from "fs"; |
| 2 | + |
| 3 | +/** |
| 4 | + * This test ensures that activity types in workflow-v1.0.json stay in sync with |
| 5 | + * the webhooks.json file from the languageservice package. |
| 6 | + * |
| 7 | + * When this test fails, it means new activity types were added to webhooks.json |
| 8 | + * that need to be handled. See docs/json-data-files.md for detailed instructions. |
| 9 | + * |
| 10 | + * Quick reference for fixing failures: |
| 11 | + * 1. Check https://docs.github.com/en/actions/writing-workflows/choosing-when-your-workflow-runs/events-that-trigger-workflows |
| 12 | + * Find the event and look at its "Activity types" table to see if the type is a valid workflow trigger. |
| 13 | + * 2. If the activity type IS a valid workflow trigger: |
| 14 | + * → Add it to the corresponding *-activity-type definition in workflow-v1.0.json |
| 15 | + * 3. If the activity type is webhook-only (not in workflow docs): |
| 16 | + * → Add it to the WEBHOOK_ONLY list below |
| 17 | + * 4. If there's a naming difference between webhook and schema: |
| 18 | + * → Add it to the NAME_MAPPINGS list below |
| 19 | + * 5. If the schema has a type not in webhooks.json: |
| 20 | + * → Add it to the SCHEMA_ONLY list below |
| 21 | + */ |
| 22 | + |
| 23 | +describe("schema-sync", () => { |
| 24 | + // Activity types that exist in webhooks.json but are intentionally NOT |
| 25 | + // supported as workflow triggers. These will be ignored when checking |
| 26 | + // webhooks → schema direction. |
| 27 | + const WEBHOOK_ONLY: Record<string, string[]> = { |
| 28 | + // check_suite: requested and rerequested are webhook-only, not valid workflow triggers |
| 29 | + // See: https://docs.github.com/en/actions/writing-workflows/choosing-when-your-workflow-runs/events-that-trigger-workflows#check_suite |
| 30 | + check_suite: ["requested", "rerequested"], |
| 31 | + |
| 32 | + // registry_package: "default" is a webhook concept, not a workflow trigger type |
| 33 | + registry_package: ["default"] |
| 34 | + }; |
| 35 | + |
| 36 | + // Activity types that exist in workflow schema but are intentionally NOT |
| 37 | + // in webhooks.json (schema-only types). These will be ignored when checking |
| 38 | + // schema → webhooks direction. |
| 39 | + const SCHEMA_ONLY: Record<string, string[]> = { |
| 40 | + // registry_package: "updated" is a valid workflow trigger per GitHub docs |
| 41 | + // but doesn't exist in webhooks.json (webhooks only has "published" and "default") |
| 42 | + // See: https://docs.github.com/en/actions/writing-workflows/choosing-when-your-workflow-runs/events-that-trigger-workflows#registry_package |
| 43 | + registry_package: ["updated"] |
| 44 | + }; |
| 45 | + |
| 46 | + // Known naming differences between webhooks.json and workflow-v1.0.json. |
| 47 | + // Key: event name, Value: { webhook: "webhookName", schema: "schemaName" } |
| 48 | + // These are treated as equivalent when comparing in both directions. |
| 49 | + const NAME_MAPPINGS: Record<string, Array<{webhook: string; schema: string}>> = { |
| 50 | + // project_column: webhooks.json uses "edited" but workflow triggers use "updated" |
| 51 | + // This is a known naming difference - they represent the same action |
| 52 | + project_column: [{webhook: "edited", schema: "updated"}] |
| 53 | + }; |
| 54 | + |
| 55 | + it("activity types in workflow-v1.0.json match webhooks.json", () => { |
| 56 | + // Load webhooks.json (relative path from the test runner CWD which is the package root) |
| 57 | + const webhooksPath = "../languageservice/src/context-providers/events/webhooks.json"; |
| 58 | + const webhooks = JSON.parse(fs.readFileSync(webhooksPath, "utf-8")) as Record<string, Record<string, unknown>>; |
| 59 | + |
| 60 | + // Load workflow-v1.0.json |
| 61 | + const schemaPath = "./src/workflow-v1.0.json"; |
| 62 | + const schema = JSON.parse(fs.readFileSync(schemaPath, "utf-8")) as { |
| 63 | + definitions: Record<string, {"allowed-values"?: string[]; description?: string}>; |
| 64 | + }; |
| 65 | + |
| 66 | + const mismatches: string[] = []; |
| 67 | + |
| 68 | + // Build mapping helpers for each event |
| 69 | + const getWebhookToSchemaMapping = (eventName: string): Map<string, string> => { |
| 70 | + const map = new Map<string, string>(); |
| 71 | + for (const mapping of NAME_MAPPINGS[eventName] || []) { |
| 72 | + map.set(mapping.webhook, mapping.schema); |
| 73 | + } |
| 74 | + return map; |
| 75 | + }; |
| 76 | + |
| 77 | + const getSchemaToWebhookMapping = (eventName: string): Map<string, string> => { |
| 78 | + const map = new Map<string, string>(); |
| 79 | + for (const mapping of NAME_MAPPINGS[eventName] || []) { |
| 80 | + map.set(mapping.schema, mapping.webhook); |
| 81 | + } |
| 82 | + return map; |
| 83 | + }; |
| 84 | + |
| 85 | + // Check both directions for each event |
| 86 | + for (const [eventName, eventData] of Object.entries(webhooks)) { |
| 87 | + const webhookTypes = Object.keys(eventData); |
| 88 | + if (webhookTypes.length === 0) continue; |
| 89 | + |
| 90 | + const schemaTypeName = `${eventName.replace(/_/g, "-")}-activity-type`; |
| 91 | + const schemaDef = schema.definitions[schemaTypeName]; |
| 92 | + |
| 93 | + // If there's no activity type definition in the schema, this event |
| 94 | + // doesn't support activity types in workflows (e.g., push, pull) |
| 95 | + if (!schemaDef || !schemaDef["allowed-values"]) continue; |
| 96 | + |
| 97 | + const schemaTypes = new Set(schemaDef["allowed-values"]); |
| 98 | + const webhookOnly = new Set(WEBHOOK_ONLY[eventName] || []); |
| 99 | + const schemaOnly = new Set(SCHEMA_ONLY[eventName] || []); |
| 100 | + const webhookToSchema = getWebhookToSchemaMapping(eventName); |
| 101 | + const schemaToWebhook = getSchemaToWebhookMapping(eventName); |
| 102 | + |
| 103 | + // Direction 1: webhooks → schema |
| 104 | + // Check that each webhook type exists in schema (or has a mapping, or is webhook-only) |
| 105 | + for (const webhookType of webhookTypes) { |
| 106 | + if (webhookOnly.has(webhookType)) continue; |
| 107 | + |
| 108 | + const mappedSchemaType = webhookToSchema.get(webhookType); |
| 109 | + if (mappedSchemaType) { |
| 110 | + // Has a mapping - check the mapped name exists in schema |
| 111 | + if (!schemaTypes.has(mappedSchemaType)) { |
| 112 | + mismatches.push( |
| 113 | + `Event "${eventName}": webhook type "${webhookType}" maps to "${mappedSchemaType}" but "${mappedSchemaType}" not found in schema` |
| 114 | + ); |
| 115 | + } |
| 116 | + } else { |
| 117 | + // No mapping - check the type exists directly |
| 118 | + if (!schemaTypes.has(webhookType)) { |
| 119 | + mismatches.push( |
| 120 | + `Event "${eventName}": missing activity type "${webhookType}" in workflow-v1.0.json (exists in webhooks.json)` |
| 121 | + ); |
| 122 | + } |
| 123 | + } |
| 124 | + } |
| 125 | + |
| 126 | + // Direction 2: schema → webhooks |
| 127 | + // Check that each schema type exists in webhooks (or has a mapping, or is schema-only) |
| 128 | + const webhookTypesSet = new Set(webhookTypes); |
| 129 | + for (const schemaType of schemaTypes) { |
| 130 | + if (schemaOnly.has(schemaType)) continue; |
| 131 | + |
| 132 | + const mappedWebhookType = schemaToWebhook.get(schemaType); |
| 133 | + if (mappedWebhookType) { |
| 134 | + // Has a mapping - check the mapped name exists in webhooks |
| 135 | + if (!webhookTypesSet.has(mappedWebhookType)) { |
| 136 | + mismatches.push( |
| 137 | + `Event "${eventName}": schema type "${schemaType}" maps to "${mappedWebhookType}" but "${mappedWebhookType}" not found in webhooks.json` |
| 138 | + ); |
| 139 | + } |
| 140 | + } else { |
| 141 | + // No mapping - check the type exists directly |
| 142 | + if (!webhookTypesSet.has(schemaType)) { |
| 143 | + mismatches.push( |
| 144 | + `Event "${eventName}": extra activity type "${schemaType}" in workflow-v1.0.json (not in webhooks.json)` |
| 145 | + ); |
| 146 | + } |
| 147 | + } |
| 148 | + } |
| 149 | + |
| 150 | + // Check that the description mentions all allowed values |
| 151 | + const activityDefName = `${eventName.replace(/_/g, "-")}-activity`; |
| 152 | + const activityDef = schema.definitions[activityDefName]; |
| 153 | + if (activityDef?.description) { |
| 154 | + for (const schemaType of schemaTypes) { |
| 155 | + if (!activityDef.description.includes(`\`${schemaType}\``)) { |
| 156 | + mismatches.push( |
| 157 | + `Event "${eventName}": description in "${activityDefName}" is missing activity type \`${schemaType}\`` |
| 158 | + ); |
| 159 | + } |
| 160 | + } |
| 161 | + } |
| 162 | + } |
| 163 | + |
| 164 | + if (mismatches.length > 0) { |
| 165 | + const errorMessage = [ |
| 166 | + "Activity type mismatches found between webhooks.json and workflow-v1.0.json:", |
| 167 | + "", |
| 168 | + ...mismatches, |
| 169 | + "", |
| 170 | + "To fix these mismatches:", |
| 171 | + "1. Check GitHub docs: https://docs.github.com/en/actions/writing-workflows/choosing-when-your-workflow-runs/events-that-trigger-workflows", |
| 172 | + "2. Verify the activity type is valid for workflow triggers", |
| 173 | + "3. Update the *-activity-type definition in workflow-parser/src/workflow-v1.0.json", |
| 174 | + "4. Update the description to list all supported activity types", |
| 175 | + "5. If there's a naming difference, add it to NAME_MAPPINGS in schema-sync.test.ts", |
| 176 | + "6. If the type is webhook-only, add it to WEBHOOK_ONLY", |
| 177 | + "7. If the type is schema-only, add it to SCHEMA_ONLY" |
| 178 | + ].join("\n"); |
| 179 | + |
| 180 | + throw new Error(errorMessage); |
| 181 | + } |
| 182 | + }); |
| 183 | +}); |
0 commit comments