-
-
Notifications
You must be signed in to change notification settings - Fork 1.2k
feat: add integration tests & refactor scripts to runners #4396
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,14 @@ | ||
| node_modules/ | ||
| .next/ | ||
| out/ | ||
| public/ | ||
| *.config.js | ||
| *.config.mjs | ||
| *.config.cts | ||
| .storybook/ | ||
| coverage/ | ||
| netlify/ | ||
| cypress/ | ||
| dist/ | ||
| build/ | ||
| next-env.d.ts |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,35 @@ | ||
| import { buildAdoptersList } from '@/scripts/adopters'; | ||
| import { logger } from '@/scripts/helpers/logger'; | ||
| import { CustomError } from '@/types/errors/CustomError'; | ||
|
|
||
| /** | ||
| * Runs the build adopters list process. | ||
| * | ||
| * This function invokes the buildAdoptersList script and handles errors, | ||
| * logging them with context and letting the top-level .catch handle process exit. | ||
| * @throws {CustomError} When the build process fails | ||
| */ | ||
| async function runBuildAdoptersList(): Promise<void> { | ||
| try { | ||
| await buildAdoptersList(); | ||
| } catch (error) { | ||
| const customError = CustomError.fromError(error, { | ||
| category: 'script', | ||
| operation: 'runBuildAdoptersList', | ||
| detail: 'Build adopters list runner failed' | ||
| }); | ||
|
|
||
| logger.error('Build adopters list runner failed', customError); | ||
|
|
||
| throw customError; | ||
| } | ||
| } | ||
|
|
||
| // Self-executing async function to handle top-level await | ||
| (async () => { | ||
| try { | ||
| await runBuildAdoptersList(); | ||
| } catch { | ||
| process.exit(1); | ||
| } | ||
| })(); |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,67 @@ | ||
| import { dirname, resolve } from 'path'; | ||
| import { fileURLToPath } from 'url'; | ||
|
|
||
| import { start as buildDashboard } from '@/scripts/dashboard/build-dashboard'; | ||
| import { logger } from '@/scripts/helpers/logger'; | ||
| import { CustomError } from '@/types/errors/CustomError'; | ||
|
|
||
| const currentFilePath = fileURLToPath(import.meta.url); | ||
| const currentDirPath = dirname(currentFilePath); | ||
|
|
||
| interface BuildDashboardOptions { | ||
| outputPath?: string; | ||
| } | ||
|
|
||
| /** | ||
| * Runs the dashboard generation process with configurable options. | ||
| * | ||
| * This function resolves the path to the dashboard.json output file, | ||
| * then invokes the buildDashboard script. It handles errors, logging them with context | ||
| * and letting the top-level .catch handle process exit. | ||
| * | ||
| * @param options - Optional configuration for output path | ||
| * @throws {CustomError} If the build process fails or an error occurs in the runner | ||
| */ | ||
| async function runBuildDashboard(options: BuildDashboardOptions = {}): Promise<void> { | ||
| try { | ||
| const outputPath = options.outputPath || resolve(currentDirPath, '../../dashboard.json'); | ||
|
|
||
| await buildDashboard(outputPath); | ||
| } catch (error) { | ||
| // Create or enhance the error with full context | ||
| const customError = | ||
| error instanceof CustomError | ||
| ? error.updateContext({ | ||
| operation: 'runBuildDashboard', | ||
| detail: `Runner failed with output path: ${options.outputPath}` | ||
| }) | ||
| : CustomError.fromError(error, { | ||
| category: 'script', | ||
| operation: 'runBuildDashboard', | ||
| detail: `Build dashboard runner failed with output path: ${options.outputPath}` | ||
| }); | ||
|
|
||
| // Log error with full context | ||
| logger.error('Build dashboard runner failed', customError); | ||
|
|
||
| throw customError; | ||
| } | ||
| } | ||
|
|
||
| // Run only in non-test environments | ||
| if (process.env.NODE_ENV === 'test') { | ||
| logger.info('Skipping dashboard build in test environment'); | ||
| } else { | ||
| // Self-executing async function to handle top-level await | ||
| (async () => { | ||
| try { | ||
| await runBuildDashboard(); | ||
| } catch (error) { | ||
| // Ensure we exit with error code | ||
| process.exit(1); | ||
| } | ||
| })(); | ||
| } | ||
|
|
||
| // Export for testing purposes | ||
| export { runBuildDashboard }; |
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,99 @@ | ||||||||||||||||||||||||||||
| import fs from 'fs'; | ||||||||||||||||||||||||||||
| import { resolve } from 'path'; | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| import { buildFinanceInfoList } from '@/scripts/finance'; | ||||||||||||||||||||||||||||
| import { logger } from '@/scripts/helpers/logger'; | ||||||||||||||||||||||||||||
| import { CustomError } from '@/types/errors/CustomError'; | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| interface BuildFinanceInfoOptions { | ||||||||||||||||||||||||||||
| currentDir?: string; | ||||||||||||||||||||||||||||
| configDir?: string; | ||||||||||||||||||||||||||||
| financeDir?: string; | ||||||||||||||||||||||||||||
| jsonDataDir?: string; | ||||||||||||||||||||||||||||
| year?: string; | ||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| const DEFAULT_OPTIONS = { | ||||||||||||||||||||||||||||
| currentDir: '.', | ||||||||||||||||||||||||||||
| configDir: 'config', | ||||||||||||||||||||||||||||
| financeDir: 'finance', | ||||||||||||||||||||||||||||
| jsonDataDir: 'json-data' | ||||||||||||||||||||||||||||
| }; | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| /** | ||||||||||||||||||||||||||||
| * Runs the build finance info list process with configurable options. | ||||||||||||||||||||||||||||
| * | ||||||||||||||||||||||||||||
| * This function locates the latest year in the finance directory, | ||||||||||||||||||||||||||||
| * then invokes the buildFinanceInfoList script for that year. | ||||||||||||||||||||||||||||
| * It handles errors, logging them with context and letting the top-level .catch handle process exit. | ||||||||||||||||||||||||||||
| * | ||||||||||||||||||||||||||||
| * @param options - Optional configuration for directories and year | ||||||||||||||||||||||||||||
| * @throws {CustomError} If the build process fails or an error occurs in the runner | ||||||||||||||||||||||||||||
| */ | ||||||||||||||||||||||||||||
| async function runBuildFinanceInfoList(options: BuildFinanceInfoOptions = {}): Promise<void> { | ||||||||||||||||||||||||||||
| const config = { ...DEFAULT_OPTIONS, ...options }; | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| const financeDir = resolve(config.currentDir, config.configDir, config.financeDir); | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| try { | ||||||||||||||||||||||||||||
| // If year is not provided, find the latest year | ||||||||||||||||||||||||||||
| let targetYear = config.year; | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| if (!targetYear) { | ||||||||||||||||||||||||||||
| const yearsList = fs | ||||||||||||||||||||||||||||
| .readdirSync(financeDir) | ||||||||||||||||||||||||||||
| // Filter out any files that are not numbers | ||||||||||||||||||||||||||||
| .filter((file) => !Number.isNaN(parseFloat(file))) | ||||||||||||||||||||||||||||
| // Sort the years in descending order | ||||||||||||||||||||||||||||
| .sort((a, b) => parseFloat(b) - parseFloat(a)); | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
|
Comment on lines
+42
to
+49
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🛠️ Refactor suggestion Robustly detect year directories (avoid parseFloat pitfalls and non-directories). parseFloat will accept names like "2024-old" and even "2024.json". Filter by directory and a strict 4-digit year. - const yearsList = fs
- .readdirSync(financeDir)
- // Filter out any files that are not numbers
- .filter((file) => !Number.isNaN(parseFloat(file)))
- // Sort the years in descending order
- .sort((a, b) => parseFloat(b) - parseFloat(a));
+ const yearsList = fs
+ .readdirSync(financeDir, { withFileTypes: true })
+ .filter((d) => d.isDirectory() && /^\d{4}$/.test(d.name))
+ .map((d) => d.name)
+ .sort((a, b) => Number(b) - Number(a));📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||
| if (yearsList.length === 0) { | ||||||||||||||||||||||||||||
| throw new CustomError('No finance data found in the finance directory', { | ||||||||||||||||||||||||||||
| category: 'script', | ||||||||||||||||||||||||||||
| operation: 'findLatestYear', | ||||||||||||||||||||||||||||
| detail: `Finance directory: ${financeDir}, Available years: ${yearsList.join(', ')}` | ||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| const [latestYear] = yearsList; | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| targetYear = latestYear; | ||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| await buildFinanceInfoList({ | ||||||||||||||||||||||||||||
| currentDir: config.currentDir, | ||||||||||||||||||||||||||||
| configDir: config.configDir, | ||||||||||||||||||||||||||||
| financeDir: config.financeDir, | ||||||||||||||||||||||||||||
| year: targetYear, | ||||||||||||||||||||||||||||
| jsonDataDir: config.jsonDataDir | ||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||
| } catch (error) { | ||||||||||||||||||||||||||||
| const customError = CustomError.fromError(error, { | ||||||||||||||||||||||||||||
| category: 'script', | ||||||||||||||||||||||||||||
| operation: 'runBuildFinanceInfoList', | ||||||||||||||||||||||||||||
| detail: `Build finance info failed for year: ${config.year}, financeDir: ${financeDir}` | ||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| logger.error('Build finance info list runner failed', customError); | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| throw customError; | ||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| // Run only in non-test environments | ||||||||||||||||||||||||||||
| if (process.env.NODE_ENV === 'test') { | ||||||||||||||||||||||||||||
| logger.info('Skipping finance info list build in test environment'); | ||||||||||||||||||||||||||||
| } else { | ||||||||||||||||||||||||||||
| // Self-executing async function to handle top-level await | ||||||||||||||||||||||||||||
| (async () => { | ||||||||||||||||||||||||||||
| try { | ||||||||||||||||||||||||||||
| await runBuildFinanceInfoList(); | ||||||||||||||||||||||||||||
| } catch (error) { | ||||||||||||||||||||||||||||
| // Ensure we exit with error code | ||||||||||||||||||||||||||||
| process.exit(1); | ||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||
| })(); | ||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| // Export for testing purposes | ||||||||||||||||||||||||||||
| export { runBuildFinanceInfoList }; | ||||||||||||||||||||||||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,59 @@ | ||
| import { dirname, resolve } from 'path'; | ||
| import { fileURLToPath } from 'url'; | ||
|
|
||
| import { buildMeetings } from '@/scripts/build-meetings'; | ||
| import { logger } from '@/scripts/helpers/logger'; | ||
| import { CustomError } from '@/types/errors/CustomError'; | ||
|
|
||
| const currentFilePath = fileURLToPath(import.meta.url); | ||
| const currentDirPath = dirname(currentFilePath); | ||
|
|
||
| interface BuildMeetingsOptions { | ||
| outputPath?: string; | ||
| } | ||
|
|
||
| /** | ||
| * Runs the build meetings process with configurable options. | ||
| * | ||
| * This function resolves the path to the meetings.json configuration file, | ||
| * then invokes the buildMeetings script. It handles errors, logging them with context | ||
| * and letting the top-level .catch handle process exit. | ||
| * | ||
| * @param options - Optional configuration for output path | ||
| * @throws {CustomError} If the build process fails or an error occurs in the runner | ||
| */ | ||
| async function runBuildMeetings(options: BuildMeetingsOptions = {}): Promise<void> { | ||
| try { | ||
| const outputPath = options.outputPath || resolve(currentDirPath, '../../config', 'meetings.json'); | ||
|
|
||
| await buildMeetings(outputPath); | ||
| } catch (error) { | ||
| const customError = CustomError.fromError(error, { | ||
| category: 'script', | ||
| operation: 'runBuildMeetings', | ||
| detail: `Build meetings failed with output path: ${options.outputPath}` | ||
| }); | ||
|
|
||
| logger.error('Build meetings runner failed', customError); | ||
|
|
||
| throw customError; | ||
| } | ||
| } | ||
|
|
||
| // Run only in non-test environments | ||
| if (process.env.NODE_ENV === 'test') { | ||
| logger.info('Skipping meetings build in test environment'); | ||
| } else { | ||
| // Self-executing async function to handle top-level await | ||
| (async () => { | ||
| try { | ||
| await runBuildMeetings(); | ||
| } catch (error) { | ||
| // Ensure we exit with error code | ||
| process.exit(1); | ||
| } | ||
| })(); | ||
| } | ||
|
|
||
| // Export for testing purposes | ||
| export { runBuildMeetings }; |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,59 @@ | ||
| import { dirname, resolve } from 'path'; | ||
| import { fileURLToPath } from 'url'; | ||
|
|
||
| import { buildNewsroomVideos } from '@/scripts/build-newsroom-videos'; | ||
| import { logger } from '@/scripts/helpers/logger'; | ||
| import { CustomError } from '@/types/errors/CustomError'; | ||
|
|
||
| const currentFilePath = fileURLToPath(import.meta.url); | ||
| const currentDirPath = dirname(currentFilePath); | ||
|
|
||
| interface BuildNewsroomVideosOptions { | ||
| outputPath?: string; | ||
| } | ||
|
|
||
| /** | ||
| * Runs the build newsroom videos process with configurable options. | ||
| * | ||
| * This function resolves the path to the newsroom_videos.json configuration file, | ||
| * then invokes the buildNewsroomVideos script. It handles errors, logging them with context | ||
| * and letting the top-level .catch handle process exit. | ||
| * | ||
| * @param options - Optional configuration for output path | ||
| * @throws {CustomError} If the build process fails or an error occurs in the runner | ||
| */ | ||
| async function runBuildNewsroomVideos(options: BuildNewsroomVideosOptions = {}): Promise<void> { | ||
| try { | ||
| const outputPath = options.outputPath || resolve(currentDirPath, '../../config', 'newsroom_videos.json'); | ||
|
|
||
| await buildNewsroomVideos(outputPath); | ||
| } catch (error) { | ||
| const customError = CustomError.fromError(error, { | ||
| category: 'script', | ||
| operation: 'runBuildNewsroomVideos', | ||
| detail: `Build newsroom videos failed with output path: ${options.outputPath}` | ||
| }); | ||
|
|
||
| logger.error('Build newsroom videos runner failed', customError); | ||
|
|
||
| throw customError; | ||
| } | ||
| } | ||
|
|
||
| // Run only in non-test environments | ||
| if (process.env.NODE_ENV === 'test') { | ||
| logger.info('Skipping newsroom videos build in test environment'); | ||
| } else { | ||
| // Self-executing async function to handle top-level await | ||
| (async () => { | ||
| try { | ||
| await runBuildNewsroomVideos(); | ||
| } catch (error) { | ||
| // Ensure we exit with error code | ||
| process.exit(1); | ||
| } | ||
| })(); | ||
| } | ||
|
|
||
| // Export for testing purposes | ||
| export { runBuildNewsroomVideos }; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
Avoid negation in testMatch; move exclusions to testPathIgnorePatterns.
Jest’s testMatch is for positive globs; use testPathIgnorePatterns for excludes to prevent accidental test discovery.
Apply:
🤖 Prompt for AI Agents