basant307/AI_Governance_Project
045
1/**2 * @license3 * Copyright 2025 Google LLC4 * SPDX-License-Identifier: Apache-2.05 */6 7import { describe, it, expect, beforeEach, afterEach } from 'vitest';8import * as fs from 'node:fs';9import * as path from 'node:path';10import * as os from 'node:os';11import { writeWithBackup, writeWithBackupSync } from './writeWithBackup.js';12 13describe('writeWithBackup', () => {14 let tempDir: string;15 16 beforeEach(() => {17 tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'writeWithBackup-test-'));18 });19 20 afterEach(() => {21 // Clean up temp directory22 try {23 fs.rmSync(tempDir, { recursive: true, force: true });24 } catch (_e) {25 // Ignore cleanup errors26 }27 });28 29 describe('writeWithBackupSync', () => {30 it('should write content to a new file', () => {31 const targetPath = path.join(tempDir, 'test-file.txt');32 const content = 'Hello, World!';33 34 writeWithBackupSync(targetPath, content);35 36 expect(fs.existsSync(targetPath)).toBe(true);37 expect(fs.readFileSync(targetPath, 'utf-8')).toBe(content);38 });39 40 it('should not leave a backup file behind after a successful overwrite', () => {41 const targetPath = path.join(tempDir, 'test-file.txt');42 const originalContent = 'Original content';43 const newContent = 'New content';44 45 fs.writeFileSync(targetPath, originalContent);46 writeWithBackupSync(targetPath, newContent);47 48 // Target has the new content; the .orig safety net is cleaned up on49 // success so it does not pollute the directory.50 expect(fs.readFileSync(targetPath, 'utf-8')).toBe(newContent);51 expect(fs.existsSync(`${targetPath}.orig`)).toBe(false);52 });53 54 it('should not accumulate backups across repeated writes', () => {55 const targetPath = path.join(tempDir, 'test-file.txt');56 57 fs.writeFileSync(targetPath, 'v0');58 writeWithBackupSync(targetPath, 'v1');59 writeWithBackupSync(targetPath, 'v2');60 writeWithBackupSync(targetPath, 'v3');61 62 expect(fs.readFileSync(targetPath, 'utf-8')).toBe('v3');63 // Only the target remains in the directory.64 expect(fs.readdirSync(tempDir)).toEqual(['test-file.txt']);65 });66 67 it('should clean up the backup honoring a custom suffix', () => {68 const targetPath = path.join(tempDir, 'test-file.txt');69 70 fs.writeFileSync(targetPath, 'Original');71 writeWithBackupSync(targetPath, 'New', { backupSuffix: '.bak' });72 73 expect(fs.readFileSync(targetPath, 'utf-8')).toBe('New');74 expect(fs.existsSync(`${targetPath}.bak`)).toBe(false);75 expect(fs.existsSync(`${targetPath}.orig`)).toBe(false);76 });77 78 it('should clean up temp file on failure', () => {79 const targetPath = path.join(tempDir, 'test-file.txt');80 const tempPath = `${targetPath}.tmp`;81 82 // Create a situation where rename will fail (e.g., by creating a directory at target)83 fs.mkdirSync(targetPath);84 85 expect(() => writeWithBackupSync(targetPath, 'content')).toThrow();86 expect(fs.existsSync(tempPath)).toBe(false);87 });88 89 it('should preserve original file content when write fails after backup', () => {90 const targetPath = path.join(tempDir, 'test-file.txt');91 const originalContent = 'Original content that must be preserved';92 93 // Create original file94 fs.writeFileSync(targetPath, originalContent);95 96 // Create a situation where rename will fail (by creating a directory at temp path)97 const tempPath = `${targetPath}.tmp`;98 fs.mkdirSync(tempPath);99 100 // The write should fail101 expect(() => writeWithBackupSync(targetPath, 'New content')).toThrow();102 103 // Original file should still exist with original content104 expect(fs.existsSync(targetPath)).toBe(true);105 expect(fs.statSync(targetPath).isFile()).toBe(true);106 expect(fs.readFileSync(targetPath, 'utf-8')).toBe(originalContent);107 108 // Cleanup109 fs.rmdirSync(tempPath);110 });111 112 it('should remove the backup once the target is updated', () => {113 const targetPath = path.join(tempDir, 'test-file.txt');114 const backupPath = `${targetPath}.orig`;115 const originalContent = 'Original content';116 const newContent = 'New content';117 118 fs.writeFileSync(targetPath, originalContent);119 writeWithBackupSync(targetPath, newContent);120 121 // The backup was only an in-flight safety net; it is gone on success.122 expect(fs.existsSync(backupPath)).toBe(false);123 expect(fs.readFileSync(targetPath, 'utf-8')).toBe(newContent);124 });125 126 it('should include recovery information in error message', () => {127 const targetPath = path.join(tempDir, 'test-file.txt');128 129 // Create a situation where rename will fail (directory at target)130 fs.mkdirSync(targetPath);131 132 let errorMessage = '';133 try {134 writeWithBackupSync(targetPath, 'content');135 } catch (error) {136 errorMessage = error instanceof Error ? error.message : String(error);137 }138 139 // Error message should be descriptive140 expect(errorMessage).toContain('directory');141 expect(errorMessage.length).toBeGreaterThan(10);142 });143 144 it('should handle backup failure with descriptive error', () => {145 const targetPath = path.join(tempDir, 'test-file.txt');146 const backupPath = `${targetPath}.orig`;147 const originalContent = 'Original content';148 149 // Create original file150 fs.writeFileSync(targetPath, originalContent);151 152 // Create a directory at backup path to cause backup to fail153 fs.mkdirSync(backupPath);154 155 let errorMessage = '';156 try {157 writeWithBackupSync(targetPath, 'New content');158 } catch (error) {159 errorMessage = error instanceof Error ? error.message : String(error);160 }161 162 // Error message should mention backup failure163 expect(errorMessage).toContain('backup');164 165 // Original file should still exist166 expect(fs.existsSync(targetPath)).toBe(true);167 expect(fs.readFileSync(targetPath, 'utf-8')).toBe(originalContent);168 169 // Cleanup170 fs.rmdirSync(backupPath);171 });172 173 it('should clean up temp file when backup creation fails', () => {174 const targetPath = path.join(tempDir, 'test-file.txt');175 const tempPath = `${targetPath}.tmp`;176 const backupPath = `${targetPath}.orig`;177 const originalContent = 'Original content';178 179 // Create original file180 fs.writeFileSync(targetPath, originalContent);181 182 // Create a directory at backup path to cause backup to fail183 fs.mkdirSync(backupPath);184 185 // The write should fail186 expect(() => writeWithBackupSync(targetPath, 'New content')).toThrow();187 188 // Temp file should be cleaned up189 expect(fs.existsSync(tempPath)).toBe(false);190 191 // Cleanup192 fs.rmdirSync(backupPath);193 });194 });195 196 describe('writeWithBackup (async)', () => {197 it('should write content to a new file', async () => {198 const targetPath = path.join(tempDir, 'test-file.txt');199 const content = 'Hello, World!';200 201 await writeWithBackup(targetPath, content);202 203 expect(fs.existsSync(targetPath)).toBe(true);204 expect(fs.readFileSync(targetPath, 'utf-8')).toBe(content);205 });206 207 it('should overwrite without leaving a backup behind', async () => {208 const targetPath = path.join(tempDir, 'test-file.txt');209 const originalContent = 'Original content';210 const newContent = 'New content';211 212 fs.writeFileSync(targetPath, originalContent);213 await writeWithBackup(targetPath, newContent);214 215 expect(fs.readFileSync(targetPath, 'utf-8')).toBe(newContent);216 expect(fs.existsSync(`${targetPath}.orig`)).toBe(false);217 });218 219 it('should use custom encoding', async () => {220 const targetPath = path.join(tempDir, 'test-file.txt');221 const content = 'Hello, World!';222 223 await writeWithBackup(targetPath, content, { encoding: 'utf8' });224 225 expect(fs.readFileSync(targetPath, 'utf-8')).toBe(content);226 });227 });228});229 