diff --git a/src/core/projectManager.ts b/src/core/projectManager.ts index 45c6625..f30e583 100644 --- a/src/core/projectManager.ts +++ b/src/core/projectManager.ts @@ -1,6 +1,6 @@ import { existsSync, readFileSync, writeFileSync, mkdirSync } from 'fs'; import { join, basename } from 'path'; -import { ProjectState, KeepFile, DecryptKey, SyncState, CapyError, ERROR_CODES } from '../types/index'; +import { ProjectState, KeepFile, SyncState, CapyError, ERROR_CODES } from '../types/index'; export class ProjectManager { private projectRoot: string; @@ -11,11 +11,9 @@ export class ProjectManager { async detectProjectState(): Promise { const keepPath = this.getKeepPath(); - const decryptPath = this.getDecryptPath(); const envPath = this.getEnvPath(); const hasKeepFile = existsSync(keepPath); - const hasDecryptKey = existsSync(decryptPath); const hasEnvFile = existsSync(envPath); let projectName: string | undefined; @@ -45,7 +43,6 @@ export class ProjectManager { return { initialized: hasKeepFile, hasKeepFile, - hasDecryptKey, hasEnvFile, projectName, organizationId, @@ -63,10 +60,6 @@ export class ProjectManager { return join(this.projectRoot, '.capy'); } - getDecryptPath(): string { - return join(this.getCapyDir(), 'decrypt'); - } - getActiveBranchPath(): string { return join(this.getCapyDir(), 'branch'); } @@ -153,24 +146,6 @@ export class ProjectManager { } } - readDecryptKey(): DecryptKey | null { - const decryptPath = this.getDecryptPath(); - if (!existsSync(decryptPath)) { - return null; - } - - try { - const content = readFileSync(decryptPath, 'utf-8'); - return JSON.parse(content) as DecryptKey; - } catch (error) { - throw new CapyError( - 'Failed to read .decrypt file', - ERROR_CODES.INVALID_FORMAT, - { error, path: decryptPath } - ); - } - } - readSyncState(): SyncState | null { const syncStatePath = this.getSyncStatePath(); if (!existsSync(syncStatePath)) { diff --git a/src/files/fileManager.ts b/src/files/fileManager.ts index 99bbdc3..77e2c48 100644 --- a/src/files/fileManager.ts +++ b/src/files/fileManager.ts @@ -1,6 +1,6 @@ import { readFileSync, writeFileSync, existsSync, appendFileSync, chmodSync, mkdirSync, unlinkSync } from 'fs'; import { join, dirname } from 'path'; -import { EnvVariable, KeepFile, DecryptKey, SyncState, CapyError, ERROR_CODES } from '../types/index'; +import { EnvVariable, KeepFile, SyncState, CapyError, ERROR_CODES } from '../types/index'; import { parse as parseDotenv } from 'dotenv'; import { Encryptor } from '../crypto/encryptor'; import { deriveResourceId } from '../crypto/resourceId'; @@ -208,32 +208,6 @@ export class FileManager { } } - writeDecryptKey(decryptKey: DecryptKey): void { - const capyDir = join(this.projectRoot, '.capy'); - this.ensureDirectoryExists(capyDir); - - const decryptPath = join(capyDir, 'decrypt'); - const backup = this.createBackup(decryptPath); - - try { - const content = JSON.stringify(decryptKey, null, 2); - writeFileSync(decryptPath, content + '\n', { encoding: 'utf-8', mode: 0o600 }); - - if (backup) { - this.removeBackup(backup); - } - } catch (error) { - if (backup) { - this.restoreBackup(backup, decryptPath); - } - throw new CapyError( - 'Failed to write decrypt file', - ERROR_CODES.PERMISSION_DENIED, - { error, path: decryptPath } - ); - } - } - writeSyncState(syncState: SyncState): void { const capyDir = join(this.projectRoot, '.capy'); this.ensureDirectoryExists(capyDir); diff --git a/src/sync/syncEngine.ts b/src/sync/syncEngine.ts index f1d07ee..9a56605 100644 --- a/src/sync/syncEngine.ts +++ b/src/sync/syncEngine.ts @@ -7,7 +7,6 @@ import { SyncResult, KeepFile, KeepVariableEntry, - DecryptKey, SyncState, } from '../types/index'; @@ -255,27 +254,6 @@ export class SyncEngine { return updatedKeep; } - createDecryptKey( - organizationId: string, - projectId: string, - userId: string, - decryptionKey: string, - variableNames: string[] - ): DecryptKey { - const expiresAt = new Date(); - expiresAt.setDate(expiresAt.getDate() + 30); // 30 days expiration - - return { - version: '1.0', - org_id: organizationId, - project_id: projectId, - user_id: userId, - decryption_key: decryptionKey, - expires_at: expiresAt.toISOString(), - permissions: variableNames - }; - } - formatSyncSummary(changeSet: ChangeSet): string { const lines: string[] = []; diff --git a/src/types/index.ts b/src/types/index.ts index a832e21..79b150d 100644 --- a/src/types/index.ts +++ b/src/types/index.ts @@ -51,20 +51,9 @@ export interface EnvVariable { encrypted: boolean; } -export interface DecryptKey { - version: string; - org_id: string; - project_id: string; - user_id: string; - decryption_key: string; - expires_at: string; - permissions: string[]; -} - export interface ProjectState { initialized: boolean; hasKeepFile: boolean; - hasDecryptKey: boolean; hasEnvFile: boolean; projectName?: string; organizationId?: string; diff --git a/tests/commands/branchKeepFile.test.ts b/tests/commands/branchKeepFile.test.ts index eccadc1..5ebc39f 100644 --- a/tests/commands/branchKeepFile.test.ts +++ b/tests/commands/branchKeepFile.test.ts @@ -454,7 +454,6 @@ describe('Direction detection — branch-aware keep_hash', () => { getDefaultProjectName: mock(() => 'test-project'), getEnvPath: mock(() => '.env'), readKeepFile: mock(() => undefined), - readDecryptKey: mock(() => undefined), readSyncState: mock(() => null), readActiveBranch: mock(() => null), writeActiveBranch: mock(() => undefined), @@ -462,7 +461,6 @@ describe('Direction detection — branch-aware keep_hash', () => { }; mockFileManager = { writeKeepFile: mock(() => undefined), - writeDecryptKey: mock(() => undefined), writeSyncState: mock(() => undefined), writeEncryptedEnvFile: mock(() => undefined), readEnvFile: mock(() => ({})), @@ -500,7 +498,6 @@ describe('Direction detection — branch-aware keep_hash', () => { listProjects: mock(() => Promise.resolve([])), }; mockSyncEngine = { - createDecryptKey: mock(() => 'mock-decrypt-key'), compareEnvironments: mock(() => undefined), formatSyncSummary: mock(() => undefined), validateDecisions: mock(() => []), @@ -541,7 +538,6 @@ describe('Direction detection — branch-aware keep_hash', () => { const projectState = { initialized: true, hasKeepFile: true, - hasDecryptKey: true, hasEnvFile: true, projectName: 'test-project', projectId: 'proj-123', diff --git a/tests/commands/capyCommand.test.ts b/tests/commands/capyCommand.test.ts index cc284f5..d0a43c1 100644 --- a/tests/commands/capyCommand.test.ts +++ b/tests/commands/capyCommand.test.ts @@ -102,7 +102,6 @@ describe('CapyCommand', () => { getDefaultProjectName: mock(() => 'test-project'), getEnvPath: mock(() => '.env'), readKeepFile: mock(() => undefined), - readDecryptKey: mock(() => undefined), readSyncState: mock(() => null), readActiveBranch: mock(() => null), writeActiveBranch: mock(() => undefined), @@ -111,7 +110,6 @@ describe('CapyCommand', () => { mockFileManager = { writeKeepFile: mock(() => undefined), - writeDecryptKey: mock(() => undefined), writeSyncState: mock(() => undefined), writeEncryptedEnvFile: mock(() => undefined), readEnvFile: mock(() => ({})), @@ -152,7 +150,6 @@ describe('CapyCommand', () => { } as any; mockSyncEngine = { - createDecryptKey: mock(() => 'mock-decrypt-key'), compareEnvironments: mock(() => undefined), formatSyncSummary: mock(() => undefined), validateDecisions: mock(() => []), @@ -214,7 +211,6 @@ describe('CapyCommand', () => { mockProjectManager.detectProjectState.mockResolvedValue({ initialized: false, hasKeepFile: false, - hasDecryptKey: false, hasEnvFile: false, projectName: undefined, projectId: undefined, @@ -233,7 +229,6 @@ describe('CapyCommand', () => { const projectState = { initialized: true, hasKeepFile: true, - hasDecryptKey: true, hasEnvFile: true, projectName: 'test-project', projectId: 'proj-123', @@ -253,7 +248,6 @@ describe('CapyCommand', () => { mockProjectManager.detectProjectState.mockResolvedValue({ initialized: true, hasKeepFile: true, - hasDecryptKey: false, hasEnvFile: false, projectName: 'existing-project', projectId: 'proj-123', @@ -736,7 +730,6 @@ describe('CapyCommand', () => { const mockProjectState = { initialized: true, hasKeepFile: true, - hasDecryptKey: true, hasEnvFile: true, projectName: 'test-project', projectId: 'proj-123', @@ -834,7 +827,6 @@ describe('CapyCommand', () => { const mockProjectState = { initialized: true, hasKeepFile: true, - hasDecryptKey: true, hasEnvFile: true, projectName: 'test-project', projectId: 'proj-123', @@ -1055,7 +1047,6 @@ describe('CapyCommand', () => { const mockProjectState = { initialized: true, hasKeepFile: true, - hasDecryptKey: true, hasEnvFile: true, projectName: 'test-project', projectId: 'proj-123', diff --git a/tests/core/projectManager.test.ts b/tests/core/projectManager.test.ts index fbd2084..6c3eb4d 100644 --- a/tests/core/projectManager.test.ts +++ b/tests/core/projectManager.test.ts @@ -29,7 +29,6 @@ describe('ProjectManager', () => { test('should detect uninitialized project when no keep.lock file exists', async () => { mockExistsSync.mockImplementation((path) => { if (path === join(testRoot, 'keep.lock')) return false; - if (path === join(testRoot, '.capy/decrypt')) return false; if (path === join(testRoot, '.env')) return true; return false; }); @@ -39,7 +38,6 @@ describe('ProjectManager', () => { expect(state).toMatchObject({ initialized: false, hasKeepFile: false, - hasDecryptKey: false, hasEnvFile: true, activeBranch: 'development', }); @@ -56,7 +54,6 @@ describe('ProjectManager', () => { mockExistsSync.mockImplementation((path) => { if (path === join(testRoot, 'keep.lock')) return true; - if (path === join(testRoot, '.capy/decrypt')) return true; if (path === join(testRoot, '.env')) return true; return false; }); @@ -68,7 +65,6 @@ describe('ProjectManager', () => { expect(state).toMatchObject({ initialized: true, hasKeepFile: true, - hasDecryptKey: true, hasEnvFile: true, projectName: 'test-project', organizationId: 'org_123', @@ -270,11 +266,6 @@ describe('ProjectManager', () => { expect(path).toBe(join(testRoot, 'keep.lock')); }); - test('should return correct decrypt path', () => { - const path = (projectManager as any).getDecryptPath(); - expect(path).toBe(join(testRoot, '.capy/decrypt')); - }); - test('should return correct env path', () => { const path = (projectManager as any).getEnvPath(); expect(path).toBe(join(testRoot, '.env')); diff --git a/tests/files/fileManager.test.ts b/tests/files/fileManager.test.ts index 0ff166c..10f9a55 100644 --- a/tests/files/fileManager.test.ts +++ b/tests/files/fileManager.test.ts @@ -21,7 +21,7 @@ afterAll(() => { mock.restore(); }); import { join, dirname } from 'path'; import { FileManager } from '../../src/files/fileManager'; -import { KeepFile, DecryptKey, CapyError, ERROR_CODES } from '../../src/types/index'; +import { KeepFile, CapyError, ERROR_CODES } from '../../src/types/index'; describe('FileManager', () => { let fileManager: FileManager; @@ -222,53 +222,9 @@ describe('FileManager', () => { }); }); - describe('writeDecryptKey', () => { - test('should write decrypt key with secure permissions', () => { - const decryptKey: DecryptKey = { - version: '1.0', - org_id: 'org_123', - project_id: 'proj_456', - user_id: 'user_789', - decryption_key: 'key_abc', - expires_at: '2024-02-01T00:00:00Z', - permissions: ['VAR1', 'VAR2'] - }; - - mockExistsSync.mockReturnValue(false); // No backup needed - - fileManager.writeDecryptKey(decryptKey); - - expect(mockWriteFileSync).toHaveBeenCalledWith( - join(testRoot, '.capy/decrypt'), - JSON.stringify(decryptKey, null, 2) + '\n', - { encoding: 'utf-8', mode: 0o600 } - ); - }); - - test('should handle write failure with error throwing', () => { - const decryptKey: DecryptKey = { - version: '1.0', - org_id: 'org_123', - project_id: 'proj_456', - user_id: 'user_789', - decryption_key: 'key_abc', - expires_at: '2024-02-01T00:00:00Z', - permissions: [] - }; - - mockExistsSync.mockReturnValue(false); - mockWriteFileSync.mockImplementation(() => { - throw new Error('Permission denied'); - }); - - expect(() => fileManager.writeDecryptKey(decryptKey)).toThrow(CapyError); - expect(() => fileManager.writeDecryptKey(decryptKey)).toThrow('Failed to write decrypt file'); - }); - }); - describe('updateGitignore', () => { test('should add new entries to .gitignore', () => { - const entries = ['.env', '.capy/decrypt']; + const entries = ['.env']; mockExistsSync.mockReturnValue(false); // No existing .gitignore fileManager.updateGitignore(entries); @@ -280,14 +236,14 @@ describe('FileManager', () => { ); expect(mockAppendFileSync).toHaveBeenCalledWith( join(testRoot, '.gitignore'), - '.env\n.capy/decrypt\n', + '.env\n', 'utf-8' ); }); test('should not add duplicate entries', () => { - const entries = ['.env', '.capy/decrypt']; - const existingContent = '.env\nnode_modules\n.capy/decrypt\n'; + const entries = ['.env']; + const existingContent = '.env\nnode_modules\n'; mockExistsSync.mockReturnValue(true); mockReadFileSync.mockReturnValue(existingContent); @@ -299,7 +255,7 @@ describe('FileManager', () => { }); test('should add only missing entries', () => { - const entries = ['.env', '.capy/decrypt', '.capy/token']; + const entries = ['.env', '.capy/token']; const existingContent = '.env\nnode_modules\n'; mockExistsSync.mockReturnValue(true); @@ -314,7 +270,7 @@ describe('FileManager', () => { ); expect(mockAppendFileSync).toHaveBeenCalledWith( join(testRoot, '.gitignore'), - '.capy/decrypt\n.capy/token\n', + '.capy/token\n', 'utf-8' ); }); diff --git a/tests/integration/cliIntegration.test.ts b/tests/integration/cliIntegration.test.ts index 0275c96..8757778 100644 --- a/tests/integration/cliIntegration.test.ts +++ b/tests/integration/cliIntegration.test.ts @@ -6,7 +6,7 @@ import { SyncEngine } from '../../src/sync/syncEngine'; import { PromptEngine } from '../../src/ui/promptEngine'; import { AuthService } from '../../src/auth/authService'; import { ServiceClient } from '../../src/service/serviceClient'; -import { CapyError, KeepFile, DecryptKey } from '../../src/types/index'; +import { CapyError, KeepFile } from '../../src/types/index'; import * as fs from 'fs'; // Integration tests - testing components working together without full mocking @@ -44,7 +44,6 @@ describe('CLI Integration Tests', () => { // Mock file system operations since we're not actually creating files spyOn(fs, 'existsSync').mockImplementation((path: any) => { if (path.includes('keep.lock')) return false; - if (path.includes('.capy/decrypt')) return false; if (path.includes('.env')) return true; return false; }); @@ -54,7 +53,6 @@ describe('CLI Integration Tests', () => { expect(state).toMatchObject({ initialized: false, hasKeepFile: false, - hasDecryptKey: false, hasEnvFile: true, activeBranch: 'development', }); @@ -71,7 +69,6 @@ describe('CLI Integration Tests', () => { spyOn(fs, 'existsSync').mockImplementation((path: any) => { if (path.includes('keep.lock')) return true; - if (path.includes('.capy/decrypt')) return true; if (path.includes('.env')) return true; return false; }); @@ -83,7 +80,6 @@ describe('CLI Integration Tests', () => { expect(state).toMatchObject({ initialized: true, hasKeepFile: true, - hasDecryptKey: true, hasEnvFile: true, projectName: 'test-project', organizationId: 'org_123', @@ -135,27 +131,6 @@ DEBUG=true`; ); }); - test('should create decrypt key with secure permissions', () => { - const mockDecryptKey: DecryptKey = { - version: '1.0', - org_id: 'org_123', - project_id: 'proj_456', - user_id: 'user_789', - decryption_key: 'decrypt_key_abc', - expires_at: new Date(Date.now() + 30 * 24 * 60 * 60 * 1000).toISOString(), - permissions: ['API_KEY', 'DB_URL'] - }; - - const writeFileSyncSpy = spyOn(fs, 'writeFileSync').mockImplementation(() => {}); - - fileManager.writeDecryptKey(mockDecryptKey); - - expect(writeFileSyncSpy).toHaveBeenCalledWith( - expect.stringContaining('.capy/decrypt'), - JSON.stringify(mockDecryptKey, null, 2) + '\n', - { encoding: 'utf-8', mode: 0o600 } - ); - }); }); describe('Sync Engine Integration', () => { @@ -343,33 +318,6 @@ DEBUG=true`; }); }); - describe('Decrypt Key Creation Integration', () => { - test('should create valid decrypt key with proper expiration', () => { - const orgId = 'org_123'; - const projectId = 'proj_456'; - const userId = 'user_789'; - const decryptionKey = 'decrypt_key_abc'; - const permissions = ['API_KEY', 'DB_URL']; - - const decryptKey = syncEngine.createDecryptKey(orgId, projectId, userId, decryptionKey, permissions); - - expect(decryptKey.version).toBe('1.0'); - expect(decryptKey.org_id).toBe(orgId); - expect(decryptKey.project_id).toBe(projectId); - expect(decryptKey.user_id).toBe(userId); - expect(decryptKey.decryption_key).toBe(decryptionKey); - expect(decryptKey.permissions).toEqual(permissions); - - // Verify expiration is approximately 30 days from now - const expiresAt = new Date(decryptKey.expires_at); - const expectedExpiration = new Date(); - expectedExpiration.setDate(expectedExpiration.getDate() + 30); - - const timeDiff = Math.abs(expiresAt.getTime() - expectedExpiration.getTime()); - expect(timeDiff).toBeLessThan(1000); // Less than 1 second difference - }); - }); - afterEach(() => { // Clean up mocks jest.restoreAllMocks(); // Bun supports this via jest global diff --git a/tests/sync/syncEngine.test.ts b/tests/sync/syncEngine.test.ts index 40f9974..abc8738 100644 --- a/tests/sync/syncEngine.test.ts +++ b/tests/sync/syncEngine.test.ts @@ -395,33 +395,6 @@ describe('SyncEngine', () => { }); }); - describe('createDecryptKey', () => { - test('should create decrypt key with proper structure', () => { - const orgId = 'org_123'; - const projectId = 'proj_456'; - const userId = 'user_789'; - const decryptionKey = 'decrypt_key_abc'; - const permissions = ['VAR1', 'VAR2']; - - const result = syncEngine.createDecryptKey(orgId, projectId, userId, decryptionKey, permissions); - - expect(result.version).toBe('1.0'); - expect(result.org_id).toBe(orgId); - expect(result.project_id).toBe(projectId); - expect(result.user_id).toBe(userId); - expect(result.decryption_key).toBe(decryptionKey); - expect(result.permissions).toEqual(permissions); - expect(result.expires_at).toBeDefined(); - - // Check that expiration is 30 days from now - const expiresAt = new Date(result.expires_at); - const expectedExpiration = new Date(); - expectedExpiration.setDate(expectedExpiration.getDate() + 30); - - expect(Math.abs(expiresAt.getTime() - expectedExpiration.getTime())).toBeLessThan(1000); - }); - }); - describe('formatSyncSummary', () => { test('should format comprehensive sync summary', () => { const changeSet: ChangeSet = {