chromium/ui/file_manager/file_manager/background/js/crostini_unittest.ts

// Copyright 2018 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

import {loadTimeData} from 'chrome://resources/ash/common/load_time_data.m.js';
import {assertFalse, assertTrue} from 'chrome://webui-test/chromeos/chai_assert.js';

import {installMockChrome, MockMetrics} from '../../common/js/mock_chrome.js';
import {MockDirectoryEntry, MockEntry, MockFileSystem} from '../../common/js/mock_entry.js';
import {RootType} from '../../common/js/volume_manager_types.js';

import {Crostini} from './crostini.js';
import type {EntryLocation} from './entry_location_impl.js';
import type {VolumeManager} from './volume_manager.js';

let volumeManagerRootType: RootType;

let volumeManager: VolumeManager;

let crostini: Crostini;

// Set up the test components.
export function setUp() {
  // Mock fileManagerPrivate.onCrostiniChanged.
  const mockChrome = {
    metricsPrivate: new MockMetrics(),
    fileManagerPrivate: {
      onCrostiniChanged: {
        addListener: () => {},
      },
    },
  };
  installMockChrome(mockChrome);

  // Create a fake volume manager that provides entry location info.
  volumeManager = {
    getLocationInfo: (_: Entry) => {
      return {
        rootType: volumeManagerRootType,
      } as EntryLocation;
    },
  } as VolumeManager;

  // Reset initial root type.
  volumeManagerRootType = 'testroot' as RootType;

  // Create and initialize Crostini.
  crostini = new Crostini();
  crostini.initVolumeManager(volumeManager);
}

/**
 * Tests init sets crostini and PluginVm enabled status.
 */
export function testInitCrostiniPluginVmEnabled() {
  loadTimeData.overrideValues({'VMS_FOR_SHARING': []});
  crostini.initEnabled();
  assertFalse(crostini.isEnabled('termina'));
  assertFalse(crostini.isEnabled('PvmDefault'));

  loadTimeData.overrideValues({
    'VMS_FOR_SHARING': [
      {'vmName': 'termina', 'containerName': 'penguin'},
      {'vmName': 'PvmDefault', 'containerName': ''},
    ],
  });
  crostini.initEnabled();
  assertTrue(crostini.isEnabled('termina'));
  assertTrue(crostini.isEnabled('PvmDefault'));
}

/**
 * Tests setEnabled tracks enabled/disabled correctly, in particular with
 * multiple containers in a VM.
 */
export function testSetEnabled() {
  assertFalse(crostini.isEnabled('termina'));

  crostini.setEnabled('termina', 'penguin', true);
  assertTrue(crostini.isEnabled('termina'));

  crostini.setEnabled('termina', 'puffin', true);
  assertTrue(crostini.isEnabled('termina'));

  crostini.setEnabled('termina', 'penguin', false);
  assertTrue(crostini.isEnabled('termina'));

  crostini.setEnabled('termina', 'puffin', false);
  assertFalse(crostini.isEnabled('termina'));
}

/**
 * Tests path sharing.
 */
export function testIsPathShared() {
  const mockFileSystem = new MockFileSystem('volumeId');
  const root = MockDirectoryEntry.create(mockFileSystem, '/');
  const a = MockDirectoryEntry.create(mockFileSystem, '/a');
  const aa = MockDirectoryEntry.create(mockFileSystem, '/a/a');
  const ab = MockDirectoryEntry.create(mockFileSystem, '/a/b');
  const b = MockDirectoryEntry.create(mockFileSystem, '/b');
  const bb = MockDirectoryEntry.create(mockFileSystem, '/b/b');

  assertFalse(crostini.isPathShared('vm1', a));
  assertFalse(crostini.isPathShared('vm2', a));

  crostini.registerSharedPath('vm1', a);
  assertFalse(crostini.isPathShared('vm1', root));
  assertTrue(crostini.isPathShared('vm1', a));
  assertTrue(crostini.isPathShared('vm1', aa));

  crostini.registerSharedPath('vm2', a);
  assertTrue(crostini.isPathShared('vm2', a));

  crostini.registerSharedPath('vm1', bb);
  assertFalse(crostini.isPathShared('vm1', b));
  assertTrue(crostini.isPathShared('vm1', bb));

  crostini.unregisterSharedPath('vm1', bb);
  assertFalse(crostini.isPathShared('vm1', bb));

  // Test collapsing vm1, but not vm2.  Setup with /a/a, /a/b, /b
  crostini.unregisterSharedPath('vm1', a);
  crostini.unregisterSharedPath('vm2', a);
  crostini.registerSharedPath('vm1', aa);
  crostini.registerSharedPath('vm1', ab);
  crostini.registerSharedPath('vm1', b);
  crostini.registerSharedPath('vm2', aa);
  assertFalse(crostini.isPathShared('vm1', a));
  assertFalse(crostini.isPathShared('vm2', a));
  assertTrue(crostini.isPathShared('vm1', aa));
  assertTrue(crostini.isPathShared('vm1', ab));
  assertTrue(crostini.isPathShared('vm1', b));
  assertTrue(crostini.isPathShared('vm2', aa));
  // Add /a for vm1, collapses /a/a, /a/b in vm1.
  crostini.registerSharedPath('vm1', a);
  assertTrue(crostini.isPathShared('vm1', a));
  assertTrue(crostini.isPathShared('vm1', aa));
  assertTrue(crostini.isPathShared('vm1', ab));
  assertTrue(crostini.isPathShared('vm1', b));
  assertTrue(crostini.isPathShared('vm2', aa));
  assertFalse(crostini.isPathShared('vm2', a));
  // Unregister /a for vm1, /a/a and /a/b should be lost in vm1.
  crostini.unregisterSharedPath('vm1', a);
  assertFalse(crostini.isPathShared('vm1', a));
  assertFalse(crostini.isPathShared('vm1', aa));
  assertFalse(crostini.isPathShared('vm1', ab));
  assertTrue(crostini.isPathShared('vm1', b));
  assertTrue(crostini.isPathShared('vm2', aa));
  // Register root for vm1, collapses all vm1.
  crostini.registerSharedPath('vm1', root);
  assertTrue(crostini.isPathShared('vm1', a));
  assertTrue(crostini.isPathShared('vm1', aa));
  assertTrue(crostini.isPathShared('vm1', ab));
  assertTrue(crostini.isPathShared('vm1', b));
  // Unregister root for vm1, all vm1 should be lost.
  crostini.unregisterSharedPath('vm1', root);
  assertFalse(crostini.isPathShared('vm1', a));
  assertFalse(crostini.isPathShared('vm1', aa));
  assertFalse(crostini.isPathShared('vm1', ab));
  assertFalse(crostini.isPathShared('vm1', b));
  assertTrue(crostini.isPathShared('vm2', aa));
}

/*
 * Tests disallowed and allowed shared paths.
 */
export function testCanSharePath() {
  crostini.setEnabled('vm', '', true);

  const mockFileSystem = new MockFileSystem('test');
  const root = MockDirectoryEntry.create(mockFileSystem, '/');
  const rootFile = new MockEntry(mockFileSystem, '/file') as any as Entry;
  const rootFolder = MockDirectoryEntry.create(mockFileSystem, '/folder');
  const fooFile = new MockEntry(mockFileSystem, '/foo/file') as any as Entry;
  const fooFolder = MockDirectoryEntry.create(mockFileSystem, '/foo/folder');

  // TODO(crbug.com/40607763): Add computers_grand_root and computers when DriveFS
  // enforces allowed write paths.

  const allowed = [
    RootType.DOWNLOADS,
    RootType.REMOVABLE,
    RootType.ANDROID_FILES,
    RootType.DRIVE,
    RootType.SHARED_DRIVES_GRAND_ROOT,
    RootType.SHARED_DRIVE,
    RootType.DRIVE_SHARED_WITH_ME,
  ];
  for (const type of allowed) {
    volumeManagerRootType = type;
    // TODO(crbug.com/41456343): Sharing Play files root is disallowed until
    // we can ensure it will not also share Downloads.
    // We don't share 'Shared with me' root since it is fake.
    if ([
          RootType.ANDROID_FILES,
          RootType.DRIVE_SHARED_WITH_ME,
        ].includes(type)) {
      assertFalse(crostini.canSharePath('vm', root, true));
      assertFalse(crostini.canSharePath('vm', root, false));
    } else {
      assertTrue(crostini.canSharePath('vm', root, true));
      assertTrue(crostini.canSharePath('vm', root, false));
    }
    assertFalse(crostini.canSharePath('vm', rootFile, true));
    assertTrue(crostini.canSharePath('vm', rootFile, false));
    assertTrue(crostini.canSharePath('vm', rootFolder, true));
    assertTrue(crostini.canSharePath('vm', rootFolder, false));
    assertFalse(crostini.canSharePath('vm', fooFile, true));
    assertTrue(crostini.canSharePath('vm', fooFile, false));
    assertTrue(crostini.canSharePath('vm', fooFolder, true));
    assertTrue(crostini.canSharePath('vm', fooFolder, false));
  }

  // TODO(crbug.com/40607763): Remove when DriveFS enforces allowed write paths.
  const grandRootFolder =
      MockDirectoryEntry.create(mockFileSystem, '/Computers');
  const computerRootFolder =
      MockDirectoryEntry.create(mockFileSystem, '/Computers/My');
  const computerFolder =
      MockDirectoryEntry.create(mockFileSystem, '/Computers/My/foo');
  volumeManagerRootType = RootType.COMPUTERS_GRAND_ROOT;
  assertFalse(crostini.canSharePath('vm', root, false));
  assertFalse(crostini.canSharePath('vm', grandRootFolder, false));
  assertFalse(crostini.canSharePath('vm', computerRootFolder, false));
  assertFalse(crostini.canSharePath('vm', computerFolder, false));
  volumeManagerRootType = RootType.COMPUTER;
  assertFalse(crostini.canSharePath('vm', root, false));
  assertFalse(crostini.canSharePath('vm', grandRootFolder, false));
  assertFalse(crostini.canSharePath('vm', computerRootFolder, false));
  assertTrue(crostini.canSharePath('vm', computerFolder, false));

  // Sharing LinuxFiles is allowed for all VMs except termina.
  volumeManagerRootType = RootType.CROSTINI;
  assertTrue(crostini.canSharePath('vm', root, false));
  assertFalse(crostini.canSharePath('termina', root, false));
}