Files
promptfoo--promptfoo/test/scripts/update-changelog-version.test.ts
wehub-resource-sync 0d3cb498a3
Deploy local.promptfoo.app / Deploy to Cloudflare Pages (push) Waiting to run
Test and Publish Multi-arch Docker Image / test (push) Waiting to run
Test and Publish Multi-arch Docker Image / build-docker-and-push-digests (map[digest-suffix:linux-amd64 platform:linux/amd64 runner:ubuntu-latest]) (push) Blocked by required conditions
Test and Publish Multi-arch Docker Image / build-docker-and-push-digests (map[digest-suffix:linux-arm64 platform:linux/arm64 runner:ubuntu-24.04-arm]) (push) Blocked by required conditions
Test and Publish Multi-arch Docker Image / merge-docker-digests (push) Blocked by required conditions
Test and Publish Multi-arch Docker Image / Attest Multi-arch Image (push) Blocked by required conditions
Validate Renovate Config / Validate Renovate Configuration (push) Waiting to run
CI / Shell Format Check (push) Has been cancelled
CI / Check Ruby (3.4) (push) Has been cancelled
CI / CI Config (push) Has been cancelled
CI / Test on Node ${{ matrix.node }} and ${{ matrix.os }}${{ matrix.shard && format(' (shard {0}/3)', matrix.shard) || '' }} (push) Has been cancelled
CI / Build on Node ${{ matrix.node }} (push) Has been cancelled
CI / Style Check (push) Has been cancelled
CI / Generate Assets (push) Has been cancelled
CI / Check Python (3.14) (push) Has been cancelled
CI / Check Python (3.9) (push) Has been cancelled
CI / Build Docs (push) Has been cancelled
CI / Code Scan Action (push) Has been cancelled
CI / Site tests (push) Has been cancelled
CI / webui tests (push) Has been cancelled
CI / Run Integration Tests (push) Has been cancelled
CI / Run Smoke Tests (push) Has been cancelled
CI / Go Tests (push) Has been cancelled
CI / Share Test (push) Has been cancelled
CI / Redteam (Production API) (push) Has been cancelled
CI / Redteam (Staging API) (push) Has been cancelled
CI / GitHub Actions Lint (push) Has been cancelled
CI / Check Ruby (3.0) (push) Has been cancelled
release-please / release-please (push) Has been cancelled
release-please / build (push) Has been cancelled
release-please / publish-npm (push) Has been cancelled
release-please / publish-npm-backfill (push) Has been cancelled
release-please / docker (push) Has been cancelled
release-please / publish-code-scan-action (push) Has been cancelled
release-please / attest-code-scan-action (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 13:24:08 +08:00

475 lines
12 KiB
TypeScript

import fs from 'fs';
import os from 'os';
import path from 'path';
import { afterEach, beforeEach, describe, expect, it } from 'vitest';
const {
updateChangelog,
isEmptySection,
extractUnreleasedContent,
buildUpdatedChangelog,
CHANGELOG_CATEGORIES,
} = require('../../scripts/update-changelog-version.cjs') as {
updateChangelog: (options?: {
changelogPath?: string;
packageJsonPath?: string;
date?: string;
}) => {
success: boolean;
error?: string;
version?: string;
changelog?: string;
};
isEmptySection: (content: string) => boolean;
extractUnreleasedContent: (
changelog: string,
) => { content: string; match: RegExpMatchArray } | null;
buildUpdatedChangelog: (
changelog: string,
version: string,
unreleasedContent: string,
date: string,
) => string;
CHANGELOG_CATEGORIES: string[];
};
describe('update-changelog-version', () => {
describe('CHANGELOG_CATEGORIES', () => {
it('should include all Keep a Changelog categories', () => {
expect(CHANGELOG_CATEGORIES).toContain('Added');
expect(CHANGELOG_CATEGORIES).toContain('Changed');
expect(CHANGELOG_CATEGORIES).toContain('Deprecated');
expect(CHANGELOG_CATEGORIES).toContain('Removed');
expect(CHANGELOG_CATEGORIES).toContain('Fixed');
expect(CHANGELOG_CATEGORIES).toContain('Security');
});
it('should include custom categories', () => {
expect(CHANGELOG_CATEGORIES).toContain('Dependencies');
expect(CHANGELOG_CATEGORIES).toContain('Documentation');
expect(CHANGELOG_CATEGORIES).toContain('Tests');
});
});
describe('isEmptySection', () => {
it('should return true for empty section with only headers', () => {
const content = `### Added
### Changed
### Fixed`;
expect(isEmptySection(content)).toBe(true);
});
it('should return false when section has entries', () => {
const content = `### Added
- feat: new feature (#1234)
### Changed`;
expect(isEmptySection(content)).toBe(false);
});
it('should return true for completely empty string', () => {
expect(isEmptySection('')).toBe(true);
});
it('should return true for whitespace only', () => {
expect(isEmptySection(' \n\n ')).toBe(true);
});
it('should handle all Keep a Changelog categories', () => {
const content = `### Added
### Changed
### Deprecated
### Removed
### Fixed
### Security`;
expect(isEmptySection(content)).toBe(true);
});
it('should handle custom categories', () => {
const content = `### Dependencies
### Documentation
### Tests`;
expect(isEmptySection(content)).toBe(true);
});
it('should detect entry under Deprecated category', () => {
const content = `### Deprecated
- chore: deprecate old API (#1234)`;
expect(isEmptySection(content)).toBe(false);
});
it('should detect entry under Security category', () => {
const content = `### Security
- fix: patch XSS vulnerability (#5678)`;
expect(isEmptySection(content)).toBe(false);
});
});
describe('extractUnreleasedContent', () => {
it('should extract content from Unreleased section', () => {
const changelog = `# Changelog
## [Unreleased]
### Added
- feat: new feature (#1234)
## [1.0.0] - 2024-01-01
### Added
- feat: old feature (#100)`;
const result = extractUnreleasedContent(changelog);
expect(result).not.toBeNull();
expect(result!.content).toContain('feat: new feature (#1234)');
});
it('should return null if no Unreleased section', () => {
const changelog = `# Changelog
## [1.0.0] - 2024-01-01
### Added
- feat: feature (#100)`;
expect(extractUnreleasedContent(changelog)).toBeNull();
});
it('should return null if no versioned section after Unreleased', () => {
const changelog = `# Changelog
## [Unreleased]
### Added
- feat: new feature (#1234)`;
expect(extractUnreleasedContent(changelog)).toBeNull();
});
it('should trim whitespace from extracted content', () => {
const changelog = `# Changelog
## [Unreleased]
### Added
- feat: new feature (#1234)
## [1.0.0] - 2024-01-01`;
const result = extractUnreleasedContent(changelog);
expect(result!.content).not.toMatch(/^\s/);
expect(result!.content).not.toMatch(/\s$/);
});
});
describe('buildUpdatedChangelog', () => {
const baseChangelog = `# Changelog
## [Unreleased]
### Added
- feat: new feature (#1234)
## [1.0.0] - 2024-01-01
### Added
- feat: old feature (#100)`;
it('should create new versioned section', () => {
const result = buildUpdatedChangelog(
baseChangelog,
'1.1.0',
'### Added\n\n- feat: new feature (#1234)',
'2024-02-01',
);
expect(result).toContain('## [1.1.0] - 2024-02-01');
expect(result).toContain('feat: new feature (#1234)');
});
it('should include empty Unreleased template', () => {
const result = buildUpdatedChangelog(
baseChangelog,
'1.1.0',
'### Added\n\n- feat: new feature (#1234)',
'2024-02-01',
);
expect(result).toContain('## [Unreleased]');
expect(result).toContain('### Deprecated');
expect(result).toContain('### Security');
});
it('should clean up triple newlines', () => {
const result = buildUpdatedChangelog(
baseChangelog,
'1.1.0',
'### Added\n\n- feat: new feature (#1234)',
'2024-02-01',
);
expect(result).not.toMatch(/\n{3,}/);
});
it('should preserve existing versions', () => {
const result = buildUpdatedChangelog(
baseChangelog,
'1.1.0',
'### Added\n\n- feat: new feature (#1234)',
'2024-02-01',
);
expect(result).toContain('## [1.0.0] - 2024-01-01');
expect(result).toContain('feat: old feature (#100)');
});
it('should place new version between Unreleased and old versions', () => {
const result = buildUpdatedChangelog(
baseChangelog,
'1.1.0',
'### Added\n\n- feat: new feature (#1234)',
'2024-02-01',
);
const unreleasedIndex = result.indexOf('## [Unreleased]');
const newVersionIndex = result.indexOf('## [1.1.0]');
const oldVersionIndex = result.indexOf('## [1.0.0]');
expect(unreleasedIndex).toBeLessThan(newVersionIndex);
expect(newVersionIndex).toBeLessThan(oldVersionIndex);
});
});
describe('updateChangelog', () => {
let tempDir: string;
let changelogPath: string;
let packageJsonPath: string;
beforeEach(() => {
tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'changelog-test-'));
changelogPath = path.join(tempDir, 'CHANGELOG.md');
packageJsonPath = path.join(tempDir, 'package.json');
});
afterEach(() => {
fs.rmSync(tempDir, { recursive: true, force: true });
});
it('should successfully update changelog', () => {
fs.writeFileSync(
changelogPath,
`# Changelog
## [Unreleased]
### Added
- feat: new feature (#1234)
## [1.0.0] - 2024-01-01
### Added
- feat: old feature (#100)`,
);
fs.writeFileSync(packageJsonPath, JSON.stringify({ version: '1.1.0' }));
const result = updateChangelog({
changelogPath,
packageJsonPath,
date: '2024-02-01',
});
expect(result.success).toBe(true);
expect(result.version).toBe('1.1.0');
expect(result.changelog).toContain('## [1.1.0] - 2024-02-01');
});
it('should fail if package.json is missing', () => {
fs.writeFileSync(changelogPath, '# Changelog\n\n## [Unreleased]');
const result = updateChangelog({
changelogPath,
packageJsonPath: path.join(tempDir, 'nonexistent.json'),
date: '2024-02-01',
});
expect(result.success).toBe(false);
expect(result.error).toContain('Failed to read package.json');
});
it('should fail if changelog is missing', () => {
fs.writeFileSync(packageJsonPath, JSON.stringify({ version: '1.0.0' }));
const result = updateChangelog({
changelogPath: path.join(tempDir, 'nonexistent.md'),
packageJsonPath,
date: '2024-02-01',
});
expect(result.success).toBe(false);
expect(result.error).toContain('Failed to read CHANGELOG.md');
});
it('should fail if no version in package.json', () => {
fs.writeFileSync(changelogPath, '# Changelog\n\n## [Unreleased]');
fs.writeFileSync(packageJsonPath, JSON.stringify({ name: 'test' }));
const result = updateChangelog({
changelogPath,
packageJsonPath,
date: '2024-02-01',
});
expect(result.success).toBe(false);
expect(result.error).toBe('No version found in package.json');
});
it('should fail if no Unreleased section', () => {
fs.writeFileSync(
changelogPath,
`# Changelog
## [1.0.0] - 2024-01-01
### Added
- feat: old feature (#100)`,
);
fs.writeFileSync(packageJsonPath, JSON.stringify({ version: '1.1.0' }));
const result = updateChangelog({
changelogPath,
packageJsonPath,
date: '2024-02-01',
});
expect(result.success).toBe(false);
expect(result.error).toBe('Could not find Unreleased section in CHANGELOG.md');
});
it('should fail if Unreleased section is empty', () => {
fs.writeFileSync(
changelogPath,
`# Changelog
## [Unreleased]
### Added
### Changed
## [1.0.0] - 2024-01-01`,
);
fs.writeFileSync(packageJsonPath, JSON.stringify({ version: '1.1.0' }));
const result = updateChangelog({
changelogPath,
packageJsonPath,
date: '2024-02-01',
});
expect(result.success).toBe(false);
expect(result.error).toContain('No entries in Unreleased section');
});
it('should write updated changelog to disk', () => {
fs.writeFileSync(
changelogPath,
`# Changelog
## [Unreleased]
### Added
- feat: new feature (#1234)
## [1.0.0] - 2024-01-01`,
);
fs.writeFileSync(packageJsonPath, JSON.stringify({ version: '1.1.0' }));
updateChangelog({
changelogPath,
packageJsonPath,
date: '2024-02-01',
});
const written = fs.readFileSync(changelogPath, 'utf8');
expect(written).toContain('## [1.1.0] - 2024-02-01');
expect(written).toContain('feat: new feature (#1234)');
});
it('should handle multiple entries in different categories', () => {
fs.writeFileSync(
changelogPath,
`# Changelog
## [Unreleased]
### Added
- feat: new feature (#1234)
### Fixed
- fix: bug fix (#5678)
### Security
- fix: security patch (#9999)
## [1.0.0] - 2024-01-01`,
);
fs.writeFileSync(packageJsonPath, JSON.stringify({ version: '1.1.0' }));
const result = updateChangelog({
changelogPath,
packageJsonPath,
date: '2024-02-01',
});
expect(result.success).toBe(true);
expect(result.changelog).toContain('feat: new feature (#1234)');
expect(result.changelog).toContain('fix: bug fix (#5678)');
expect(result.changelog).toContain('fix: security patch (#9999)');
});
it('should fail on invalid JSON in package.json', () => {
fs.writeFileSync(changelogPath, '# Changelog\n\n## [Unreleased]');
fs.writeFileSync(packageJsonPath, 'not valid json');
const result = updateChangelog({
changelogPath,
packageJsonPath,
date: '2024-02-01',
});
expect(result.success).toBe(false);
expect(result.error).toContain('Failed to read package.json');
});
});
});