diff --git a/packages/kbn-check-saved-objects-cli/src/commands/test/baseline_snapshot.json b/packages/kbn-check-saved-objects-cli/src/commands/test/baseline_snapshot.json index b0ab5b5db9541..c90e1c2762ce1 100644 --- a/packages/kbn-check-saved-objects-cli/src/commands/test/baseline_snapshot.json +++ b/packages/kbn-check-saved-objects-cli/src/commands/test/baseline_snapshot.json @@ -10,7 +10,7 @@ "old-type-no-migrations": { "name": "old-type-no-migrations", "migrationVersions": [], - "hash": "4631898d09a686928fabbbe1ad0b7f2b22c44b565443408ab5d20b4dcae64e35", + "hash": "2491dd0321b2bb36278a0a7fab9adb9575d1f960cdbab5a4a8a2ed26d1f9370c", "modelVersions": [], "schemaVersions": [], "mappings": { @@ -24,7 +24,7 @@ "migrationVersions": [ "8.7.0" ], - "hash": "a7916755843d9ed712ccefb6888ebcbdd2f2f3628e058bfd174454aef644437a", + "hash": "7090f00c3ce9bf5bbce821b5b14c48ab538abe1d2f87ccc8d022cb96dde43ce1", "modelVersions": [], "schemaVersions": [ "8.7.0" @@ -38,11 +38,11 @@ "person-so-type": { "name": "person-so-type", "migrationVersions": [], - "hash": "b8a80c7a77c2d5f4d7964af4ef584a490a83d92012099e0d943ce566f9d52cfc", + "hash": "65702f72482adb0d4ab4c64978ef9510dc51766b483cc8f8525ab5aa5c8003ad", "modelVersions": [ { "version": "1", - "modelVersionHash": "e851da8aa62ba712eaa3e27bd787a982393fdde9a5627a779f48168edb0f86b0", + "modelVersionHash": "b7ee7569250dc40b80214f4d346caf2a3be065bb3059d7d69a4619af4c593bca", "changeTypes": [ "mappings_addition" ], @@ -52,8 +52,8 @@ "lastName.type" ], "schemas": { - "forwardCompatibility": "2fc7b480e391f573cce65c0da1991d8925fa9d1aa998c89b50181a47f10e71e4", - "create": "79a184ebeb6bdecdae9b2314c5ded4c9f3379da264b3512ab561a12d917de3f6" + "forwardCompatibility": "68d31f32c675678cb718a4792259243c033963a22d647960e8f7052d86476f44", + "create": "8de56583fdfc0dec65578fffec6bd929ae763d63f1aa56fd2d3b6828379ec2dc" } } ], diff --git a/packages/kbn-check-saved-objects-cli/src/commands/test/update_baseline_hashes.test.ts b/packages/kbn-check-saved-objects-cli/src/commands/test/update_baseline_hashes.test.ts new file mode 100644 index 0000000000000..654209e099cd1 --- /dev/null +++ b/packages/kbn-check-saved-objects-cli/src/commands/test/update_baseline_hashes.test.ts @@ -0,0 +1,38 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the "Elastic License + * 2.0", the "GNU Affero General Public License v3.0 only", and the "Server Side + * Public License v 1"; you may not use this file except in compliance with, at + * your election, the "Elastic License 2.0", the "GNU Affero General Public + * License v3.0 only", or the "Server Side Public License, v 1". + */ + +import { updateBaselineHashes } from './update_baseline_hashes'; + +/** + * Utility to refresh computed hashes in `baseline_snapshot.json`. + * + * Run when transitive dependency changes (e.g. `joi`) cause the + * `Check Saved Objects` CI step to fail with a false positive like: + * + * "Some modelVersions have been updated for SO type 'person-so-type' + * after they were defined: 10.1.0." + * + * Usage: + * yarn test:jest packages/kbn-check-saved-objects-cli/src/commands/test/update_baseline_hashes.test.ts + */ +it('updates baseline_snapshot.json hashes', async () => { + const { updated, path } = await updateBaselineHashes(); + + if (updated.length === 0) { + // eslint-disable-next-line no-console + console.log(`✅ baseline_snapshot.json is already up to date — no hashes changed.`); + } else { + // eslint-disable-next-line no-console + console.log( + `✅ Updated hashes for ${updated.length} type(s) in baseline_snapshot.json: ${updated.join( + ', ' + )}\n` + ` File: ${path}` + ); + } +}); diff --git a/packages/kbn-check-saved-objects-cli/src/commands/test/update_baseline_hashes.ts b/packages/kbn-check-saved-objects-cli/src/commands/test/update_baseline_hashes.ts new file mode 100644 index 0000000000000..e403cba86485f --- /dev/null +++ b/packages/kbn-check-saved-objects-cli/src/commands/test/update_baseline_hashes.ts @@ -0,0 +1,87 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the "Elastic License + * 2.0", the "GNU Affero General Public License v3.0 only", and the "Server Side + * Public License v 1"; you may not use this file except in compliance with, at + * your election, the "Elastic License 2.0", the "GNU Affero General Public + * License v3.0 only", or the "Server Side Public License, v 1". + */ + +import { readFileSync, writeFileSync } from 'fs'; +import { resolve } from 'path'; +import { takeSnapshot } from '../../snapshots/take_snapshot'; +import type { MigrationInfoRecord, MigrationSnapshot } from '../../types'; +import { TEST_TYPES } from './types'; + +const BASELINE_SNAPSHOT_PATH = resolve(__dirname, './baseline_snapshot.json'); + +/** + * Refreshes computed hashes in `baseline_snapshot.json` while preserving + * the hand-designed "before" state structure. + * + * Hashes are derived from `@kbn/config-schema` objects whose serialization + * depends on transitive dependencies (e.g. `joi`). When those dependencies + * are updated, the hashes drift and the CI `Check Saved Objects` test-mode + * fallback fails with a "modelVersions have been updated" false positive. + * + * This function: + * 1. Loads the existing baseline (preserving its structure: which types, + * which model versions, mappings, etc.). + * 2. Takes a fresh snapshot of `TEST_TYPES` to compute current hashes. + * 3. Patches only the hash-dependent fields (`hash`, `modelVersionHash`, + * `schemas`) in the baseline from the fresh snapshot. + * 4. Writes the updated baseline back to disk. + */ +export const updateBaselineHashes = async (): Promise<{ updated: string[]; path: string }> => { + const baseline: MigrationSnapshot = JSON.parse( + readFileSync(BASELINE_SNAPSHOT_PATH, { encoding: 'utf-8' }) + ); + const freshSnapshot = await takeSnapshot(TEST_TYPES); + const updated: string[] = []; + + for (const [typeName, baselineType] of Object.entries(baseline.typeDefinitions)) { + const freshType: MigrationInfoRecord | undefined = freshSnapshot.typeDefinitions[typeName]; + + if (!freshType) { + continue; + } + + let typeUpdated = false; + + // Refresh the top-level type `hash` (from `getMigrationHash`). + if (baselineType.hash !== freshType.hash) { + baselineType.hash = freshType.hash; + typeUpdated = true; + } + + // Refresh hashes inside each model version present in the baseline. + for (const baselineMV of baselineType.modelVersions) { + const freshMV = freshType.modelVersions.find((mv) => mv.version === baselineMV.version); + + if (!freshMV) { + continue; + } + + if (baselineMV.modelVersionHash !== freshMV.modelVersionHash) { + baselineMV.modelVersionHash = freshMV.modelVersionHash; + typeUpdated = true; + } + + if ( + baselineMV.schemas.forwardCompatibility !== freshMV.schemas.forwardCompatibility || + baselineMV.schemas.create !== freshMV.schemas.create + ) { + baselineMV.schemas = { ...freshMV.schemas }; + typeUpdated = true; + } + } + + if (typeUpdated) { + updated.push(typeName); + } + } + + writeFileSync(BASELINE_SNAPSHOT_PATH, JSON.stringify(baseline, null, 2) + '\n'); + + return { updated, path: BASELINE_SNAPSHOT_PATH }; +};