File size: 5,320 Bytes
f0634fb
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
import { describe, expect, it } from 'vitest';

import { FakeRuntime } from '#/runtime/fakeRuntime';
import { RuntimeWorkspaceView } from '#/runtime/runtimeWorkspaceView';

function runtime(generation: string, pathClass: 'posix' | 'win32'): FakeRuntime {
  return new FakeRuntime(
    { workspaceId: 'workspace', runtimeId: 'local', generation },
    { pathClass },
  );
}

describe('RuntimeWorkspaceView', () => {
  it('resolves posix paths within the fixed runtime roots', () => {
    const view = new RuntimeWorkspaceView(runtime('one', 'posix'), {
      workDir: '/workspace/project',
      additionalDirs: ['/shared'],
    });
    expect(view.resolve('src/index.ts')).toBe('/workspace/project/src/index.ts');
    expect(view.resolve('../shared/file.txt', '/workspace/project/src')).toBe('/workspace/project/shared/file.txt');
    expect(view.resolve('/shared/file.txt')).toBe('/shared/file.txt');
    expect(view.resolve('../../outside')).toBe('/outside');
    expect(() => view.assertAllowed(view.resolve('../../outside'))).toThrow('outside runtime workspace');
  });

  it('uses win32 path semantics and rejects sibling prefixes', () => {
    const view = new RuntimeWorkspaceView(runtime('one', 'win32'), {
      workDir: 'C:\\workspace\\project',
      additionalDirs: ['D:\\shared'],
    });
    expect(view.resolve('src\\index.ts')).toBe('C:\\workspace\\project\\src\\index.ts');
    expect(view.resolve('d:\\SHARED\\file.txt')).toBe('d:\\SHARED\\file.txt');
    expect(() => view.assertAllowed(view.resolve('C:\\workspace\\project-other\\file.txt'))).toThrow('outside runtime workspace');
  });

  it('uses provider-owned workspace root mapping', () => {
    const mapped = new FakeRuntime(
      { workspaceId: 'workspace', runtimeId: 'remote', generation: 'one' },
      {
        mapWorkspaceRoots: (roots) => ({
          workDir: roots.workDir.replace('/repo', '/remote/workspace'),
          additionalDirs: roots.additionalDirs?.map((root) => root.replace('/shared', '/remote/shared')),
        }),
      },
    );
    const view = new RuntimeWorkspaceView(mapped, {
      workDir: '/repo/project',
      additionalDirs: ['/shared/assets'],
    });
    expect(view.workDir).toBe('/remote/workspace/project');
    expect(view.additionalDirs).toEqual(['/remote/shared/assets']);
    expect(view.resolve('src/index.ts')).toBe('/remote/workspace/project/src/index.ts');
  });

  it('keeps heterogeneous provider mappings and allowlists isolated', () => {
    const posix = new FakeRuntime(
      { workspaceId: 'workspace', runtimeId: 'remote-posix', generation: 'one' },
      { mapWorkspaceRoots: () => ({ workDir: '/provider-a/repo', additionalDirs: ['/provider-a/shared'] }) },
    );
    const win32 = new FakeRuntime(
      { workspaceId: 'workspace', runtimeId: 'remote-win32', generation: 'one' },
      {
        pathClass: 'win32',
        mapWorkspaceRoots: () => ({ workDir: 'C:\\provider-b\\repo', additionalDirs: ['D:\\provider-b\\shared'] }),
      },
    );
    const roots = { workDir: '/repo', additionalDirs: ['/shared'] };
    const posixView = new RuntimeWorkspaceView(posix, roots);
    const win32View = new RuntimeWorkspaceView(win32, roots);

    expect(posixView.binding.runtimeId).toBe('remote-posix');
    expect(win32View.binding.runtimeId).toBe('remote-win32');
    expect(posixView.resolve('/provider-a/shared/file.txt')).toBe('/provider-a/shared/file.txt');
    expect(win32View.resolve('D:\\provider-b\\shared\\file.txt')).toBe('D:\\provider-b\\shared\\file.txt');
    expect(() => posixView.assertAllowed(posixView.resolve('/provider-b/shared/file.txt'))).toThrow('outside runtime workspace');
    expect(() => win32View.assertAllowed(win32View.resolve('C:\\provider-a\\repo\\file.txt'))).toThrow('outside runtime workspace');
  });

  it('translates Git Bash POSIX paths on win32 bash runtimes', () => {
    const winBash = new FakeRuntime(
      { workspaceId: 'workspace', runtimeId: 'local', generation: 'one' },
      {
        pathClass: 'win32',
        environment: {
          osKind: 'Windows',
          shellName: 'bash',
          shellPath: 'C:\\kimi-test-nonexistent\\Git\\bin\\bash.exe',
        },
      },
    );
    const view = new RuntimeWorkspaceView(winBash, {
      workDir: 'C:\\workspace\\project',
      additionalDirs: [],
    });
    expect(view.resolve('/c/workspace/project/src/index.ts')).toBe('C:\\workspace\\project\\src\\index.ts');
    expect(view.resolve('/cygdrive/c/workspace/project/package.json')).toBe(
      'C:\\workspace\\project\\package.json',
    );
    expect(() => view.assertAllowed(view.resolve('/tmp/scratch.txt'))).toThrow('outside runtime workspace');
  });

  it('deduplicates roots and preserves generation identity', () => {
    const first = new RuntimeWorkspaceView(runtime('one', 'posix'), {
      workDir: '/workspace',
      additionalDirs: ['/shared', '/shared'],
    });
    const second = new RuntimeWorkspaceView(runtime('two', 'posix'), {
      workDir: '/workspace',
      additionalDirs: ['/shared'],
    });
    expect(first.roots).toEqual(['/workspace', '/shared']);
    expect(first.binding).toEqual({ workspaceId: 'workspace', runtimeId: 'local' });
    expect(first.generation).toBe('one');
    expect(second.generation).toBe('two');
    expect(second.runtime).not.toBe(first.runtime);
  });
});