Path: blob/main/extensions/copilot/src/extension/inlineEdits/vscode-node/components/test/inlineEditDebugComponent.spec.ts
13406 views
/*---------------------------------------------------------------------------------------------1* Copyright (c) Microsoft Corporation. All rights reserved.2* Licensed under the MIT License. See License.txt in the project root for license information.3*--------------------------------------------------------------------------------------------*/45import { expect, suite, test } from 'vitest';6import { LogEntry } from '../../../../../platform/workspaceRecorder/common/workspaceLog';7import { filterLogForSensitiveFiles } from '../inlineEditDebugComponent';89suite('filter recording for sensitive files', () => {10test('should filter out settings.json files', () => {11const log: LogEntry[] = [12{13documentType: '[email protected]',14kind: 'header',15repoRootUri: 'file:///path/to/repo',16time: 1733253792609,17uuid: '233d78f2-202a-4d3e-9b90-0f1acc058125'18},19{20kind: 'documentEncountered',21id: 1,22relativePath: 'package.json',23time: 173325373533224},25{26kind: 'documentEncountered',27id: 2,28relativePath: '.vscode/settings.json',29time: 173325373534030},31{32kind: 'setContent',33id: 1,34v: 1,35content: '{ "name": "example" }',36time: 173325373533237},38{39kind: 'setContent',40id: 2,41v: 1,42content: '{ "sensitive": "data" }',43time: 173325373534044}45];4647const result = filterLogForSensitiveFiles(log);4849expect(result).toMatchInlineSnapshot(`50[51{52"documentType": "[email protected]",53"kind": "header",54"repoRootUri": "file:///path/to/repo",55"time": 1733253792609,56"uuid": "233d78f2-202a-4d3e-9b90-0f1acc058125",57},58{59"id": 1,60"kind": "documentEncountered",61"relativePath": "package.json",62"time": 1733253735332,63},64{65"content": "{ "name": "example" }",66"id": 1,67"kind": "setContent",68"time": 1733253735332,69"v": 1,70},71]72`);73});7475test('should filter out .env files and variants', () => {76const log: LogEntry[] = [77{ documentType: '[email protected]', kind: 'header', repoRootUri: 'file:///repo', time: 0, uuid: 'test' },78{ kind: 'documentEncountered', id: 1, relativePath: '.env', time: 0 },79{ kind: 'documentEncountered', id: 2, relativePath: '.env.local', time: 0 },80{ kind: 'documentEncountered', id: 3, relativePath: '.env.production', time: 0 },81{ kind: 'documentEncountered', id: 4, relativePath: 'app.env', time: 0 },82{ kind: 'documentEncountered', id: 5, relativePath: 'src/index.ts', time: 0 },83{ kind: 'setContent', id: 1, v: 1, content: 'SECRET=xyz', time: 0 },84{ kind: 'setContent', id: 5, v: 1, content: 'console.log(\'hello\')', time: 0 },85];8687const result = filterLogForSensitiveFiles(log);8889// Only header and src/index.ts should remain90expect(result.filter(e => e.kind === 'documentEncountered')).toHaveLength(1);91expect(result.find(e => e.kind === 'documentEncountered')).toMatchObject({92relativePath: 'src/index.ts'93});94});9596test('should filter out private key files', () => {97const log: LogEntry[] = [98{ documentType: '[email protected]', kind: 'header', repoRootUri: 'file:///repo', time: 0, uuid: 'test' },99{ kind: 'documentEncountered', id: 1, relativePath: 'server.pem', time: 0 },100{ kind: 'documentEncountered', id: 2, relativePath: 'private.key', time: 0 },101{ kind: 'documentEncountered', id: 3, relativePath: 'cert.p12', time: 0 },102{ kind: 'documentEncountered', id: 4, relativePath: 'cert.pfx', time: 0 },103{ kind: 'documentEncountered', id: 5, relativePath: 'readme.md', time: 0 },104];105106const result = filterLogForSensitiveFiles(log);107108expect(result.filter(e => e.kind === 'documentEncountered')).toHaveLength(1);109expect(result.find(e => e.kind === 'documentEncountered')).toMatchObject({110relativePath: 'readme.md'111});112});113114test('should filter out files in sensitive directories', () => {115const log: LogEntry[] = [116{ documentType: '[email protected]', kind: 'header', repoRootUri: 'file:///repo', time: 0, uuid: 'test' },117{ kind: 'documentEncountered', id: 1, relativePath: '.aws/credentials', time: 0 },118{ kind: 'documentEncountered', id: 2, relativePath: '.ssh/id_rsa', time: 0 },119{ kind: 'documentEncountered', id: 3, relativePath: '.gnupg/private-keys-v1.d/key.gpg', time: 0 },120{ kind: 'documentEncountered', id: 4, relativePath: '.docker/config.json', time: 0 },121{ kind: 'documentEncountered', id: 5, relativePath: 'src/aws/client.ts', time: 0 },122];123124const result = filterLogForSensitiveFiles(log);125126// Only src/aws/client.ts should remain (it's not in .aws directory)127expect(result.filter(e => e.kind === 'documentEncountered')).toHaveLength(1);128expect(result.find(e => e.kind === 'documentEncountered')).toMatchObject({129relativePath: 'src/aws/client.ts'130});131});132133test('should filter out files with sensitive name patterns', () => {134const log: LogEntry[] = [135{ documentType: '[email protected]', kind: 'header', repoRootUri: 'file:///repo', time: 0, uuid: 'test' },136{ kind: 'documentEncountered', id: 1, relativePath: 'id_rsa', time: 0 },137{ kind: 'documentEncountered', id: 2, relativePath: 'id_ed25519.pub', time: 0 },138{ kind: 'documentEncountered', id: 3, relativePath: 'app.secret.yaml', time: 0 },139{ kind: 'documentEncountered', id: 4, relativePath: 'normal_file.ts', time: 0 },140];141142const result = filterLogForSensitiveFiles(log);143144expect(result.filter(e => e.kind === 'documentEncountered')).toHaveLength(1);145expect(result.find(e => e.kind === 'documentEncountered')).toMatchObject({146relativePath: 'normal_file.ts'147});148});149150test('should filter exact password/token data files but not code files with those words', () => {151const log: LogEntry[] = [152{ documentType: '[email protected]', kind: 'header', repoRootUri: 'file:///repo', time: 0, uuid: 'test' },153// These should be filtered (exact sensitive data files)154{ kind: 'documentEncountered', id: 1, relativePath: 'password.txt', time: 0 },155{ kind: 'documentEncountered', id: 2, relativePath: 'passwords.json', time: 0 },156{ kind: 'documentEncountered', id: 3, relativePath: 'token.json', time: 0 },157{ kind: 'documentEncountered', id: 4, relativePath: 'tokens.txt', time: 0 },158// These should NOT be filtered (code files that deal with passwords/tokens)159{ kind: 'documentEncountered', id: 5, relativePath: 'passwordValidator.ts', time: 0 },160{ kind: 'documentEncountered', id: 6, relativePath: 'tokenAnalyzer.ts', time: 0 },161{ kind: 'documentEncountered', id: 7, relativePath: 'auth/refreshToken.service.ts', time: 0 },162{ kind: 'documentEncountered', id: 8, relativePath: 'utils/passwordStrength.ts', time: 0 },163];164165const result = filterLogForSensitiveFiles(log);166167const remainingPaths = result168.filter((e): e is LogEntry & { kind: 'documentEncountered' } => e.kind === 'documentEncountered')169.map(e => e.relativePath);170171// Only code files should remain172expect(remainingPaths).toEqual([173'passwordValidator.ts',174'tokenAnalyzer.ts',175'auth/refreshToken.service.ts',176'utils/passwordStrength.ts',177]);178});179180test('should filter out other sensitive config files', () => {181const log: LogEntry[] = [182{ documentType: '[email protected]', kind: 'header', repoRootUri: 'file:///repo', time: 0, uuid: 'test' },183{ kind: 'documentEncountered', id: 1, relativePath: '.vscode/launch.json', time: 0 },184{ kind: 'documentEncountered', id: 2, relativePath: '.npmrc', time: 0 },185{ kind: 'documentEncountered', id: 3, relativePath: '.gitconfig', time: 0 },186{ kind: 'documentEncountered', id: 4, relativePath: 'credentials.json', time: 0 },187{ kind: 'documentEncountered', id: 5, relativePath: 'tsconfig.json', time: 0 },188];189190const result = filterLogForSensitiveFiles(log);191192// Only tsconfig.json should remain193expect(result.filter(e => e.kind === 'documentEncountered')).toHaveLength(1);194expect(result.find(e => e.kind === 'documentEncountered')).toMatchObject({195relativePath: 'tsconfig.json'196});197});198199test('should handle Windows-style backslash paths', () => {200// Windows paths with backslashes are normalized to forward slashes before processing201// This ensures the function works correctly even when processing logs recorded on Windows202const log: LogEntry[] = [203{ documentType: '[email protected]', kind: 'header', repoRootUri: 'file:///repo', time: 0, uuid: 'test' },204{ kind: 'documentEncountered', id: 1, relativePath: '.vscode\\settings.json', time: 0 },205{ kind: 'documentEncountered', id: 2, relativePath: 'src\\index.ts', time: 0 },206{ kind: 'documentEncountered', id: 3, relativePath: 'config\\.env.local', time: 0 },207{ kind: 'documentEncountered', id: 4, relativePath: '.aws\\credentials', time: 0 },208];209210const result = filterLogForSensitiveFiles(log);211212// Only src\index.ts should remain - others are sensitive213expect(result.filter(e => e.kind === 'documentEncountered')).toHaveLength(1);214expect(result.find(e => e.kind === 'documentEncountered')).toMatchObject({215relativePath: 'src\\index.ts'216});217});218219test('should preserve non-document log entries', () => {220const log: LogEntry[] = [221{ documentType: '[email protected]', kind: 'header', repoRootUri: 'file:///repo', time: 0, uuid: 'test' },222{ kind: 'meta', data: { key: 'value' } },223{ kind: 'applicationStart', time: 0 },224{ kind: 'event', time: 0, data: {} },225{ kind: 'bookmark', time: 0 },226];227228const result = filterLogForSensitiveFiles(log);229230expect(result).toHaveLength(5);231});232233});234235236