CoolFace
Datasetpublic

basant307/AI_Governance_Project

sourceHugging Faceapache-2.0updated 2mo agoView on Hugging Face
0likes48downloads
standalone-update.test.ts426 linesDownload Raw Back to utils
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 {12  rollbackStandaloneUpdate,13  ensureBinWrapper,14  ensurePathInShellRc,15  performStandaloneUpdate,16  isSafeTarEntryPath,17} from './standalone-update.js';18 19describe('standalone-update', () => {20  let tempDir: string;21 22  beforeEach(() => {23    tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'qwen-update-test-'));24  });25 26  afterEach(() => {27    fs.rmSync(tempDir, { recursive: true, force: true });28  });29 30  describe('rollbackStandaloneUpdate', () => {31    it('returns no-old when .old directory does not exist', () => {32      const standaloneDir = path.join(tempDir, 'qwen-code');33      fs.mkdirSync(standaloneDir);34      fs.writeFileSync(35        path.join(standaloneDir, 'manifest.json'),36        JSON.stringify({37          name: '@qwen-code/qwen-code',38          target: 'darwin-arm64',39        }),40      );41 42      const result = rollbackStandaloneUpdate(standaloneDir);43      expect(result.ok).toBe(false);44      expect(result).toHaveProperty('reason', 'no-old');45    });46 47    it('returns no-manifest when .old directory has no manifest.json', () => {48      const standaloneDir = path.join(tempDir, 'qwen-code');49      const oldDir = `${standaloneDir}.old`;50      fs.mkdirSync(standaloneDir);51      fs.mkdirSync(oldDir);52      fs.writeFileSync(53        path.join(standaloneDir, 'manifest.json'),54        JSON.stringify({55          name: '@qwen-code/qwen-code',56          target: 'darwin-arm64',57        }),58      );59 60      const result = rollbackStandaloneUpdate(standaloneDir);61      expect(result.ok).toBe(false);62      expect(result).toHaveProperty('reason', 'no-manifest');63    });64 65    it('swaps current with .old directory on valid rollback', () => {66      const standaloneDir = path.join(tempDir, 'qwen-code');67      const oldDir = `${standaloneDir}.old`;68      fs.mkdirSync(standaloneDir);69      fs.mkdirSync(oldDir);70 71      fs.writeFileSync(72        path.join(standaloneDir, 'manifest.json'),73        JSON.stringify({74          name: '@qwen-code/qwen-code',75          target: 'darwin-arm64',76          version: '0.17.0',77        }),78      );79      fs.writeFileSync(path.join(standaloneDir, 'marker.txt'), 'new');80 81      fs.writeFileSync(82        path.join(oldDir, 'manifest.json'),83        JSON.stringify({84          name: '@qwen-code/qwen-code',85          target: 'darwin-arm64',86          version: '0.16.2',87        }),88      );89      fs.writeFileSync(path.join(oldDir, 'marker.txt'), 'old');90 91      const result = rollbackStandaloneUpdate(standaloneDir);92      expect(result.ok).toBe(true);93 94      const manifest = JSON.parse(95        fs.readFileSync(path.join(standaloneDir, 'manifest.json'), 'utf-8'),96      );97      expect(manifest.version).toBe('0.16.2');98      expect(99        fs.readFileSync(path.join(standaloneDir, 'marker.txt'), 'utf-8'),100      ).toBe('old');101      expect(fs.existsSync(oldDir)).toBe(false);102    });103 104    it('succeeds even with minimal manifest in .old', () => {105      const standaloneDir = path.join(tempDir, 'qwen-code');106      const oldDir = `${standaloneDir}.old`;107      fs.mkdirSync(standaloneDir);108      fs.mkdirSync(oldDir);109 110      fs.writeFileSync(111        path.join(standaloneDir, 'manifest.json'),112        JSON.stringify({ name: '@qwen-code/qwen-code', version: '0.17.0' }),113      );114      fs.writeFileSync(path.join(oldDir, 'manifest.json'), '{}');115 116      const result = rollbackStandaloneUpdate(standaloneDir);117      expect(result.ok).toBe(true);118    });119  });120 121  describe('ensureBinWrapper', () => {122    // Unix wrapper test relies on POSIX file permissions (mode bits) and123    // the SHELL env var, neither of which behave consistently on Windows.124    it.skipIf(process.platform === 'win32')(125      'creates a Unix shell wrapper script',126      () => {127        const libDir = path.join(tempDir, '.local', 'lib');128        const standaloneDir = path.join(libDir, 'qwen-code');129        fs.mkdirSync(standaloneDir, { recursive: true });130 131        // Isolate HOME so ensurePathInShellRc doesn't touch real shell rc132        const origHome = process.env['HOME'];133        const origShell = process.env['SHELL'];134        process.env['HOME'] = tempDir;135        process.env['SHELL'] = '/bin/zsh';136        try {137          ensureBinWrapper(standaloneDir, 'darwin-arm64');138        } finally {139          process.env['HOME'] = origHome;140          process.env['SHELL'] = origShell;141        }142 143        const wrapperPath = path.join(tempDir, '.local', 'bin', 'qwen');144        expect(fs.existsSync(wrapperPath)).toBe(true);145        const content = fs.readFileSync(wrapperPath, 'utf-8');146        expect(content).toContain('#!/bin/sh');147        expect(content).toContain(standaloneDir);148        const mode = fs.statSync(wrapperPath).mode;149        expect(mode & 0o111).toBeGreaterThan(0);150      },151    );152 153    it('creates a Windows cmd wrapper', () => {154      const libDir = path.join(tempDir, '.local', 'lib');155      const standaloneDir = path.join(libDir, 'qwen-code');156      fs.mkdirSync(standaloneDir, { recursive: true });157 158      ensureBinWrapper(standaloneDir, 'win-x64');159 160      const wrapperPath = path.join(tempDir, '.local', 'bin', 'qwen.cmd');161      expect(fs.existsSync(wrapperPath)).toBe(true);162      const content = fs.readFileSync(wrapperPath, 'utf-8');163      expect(content).toContain('@echo off');164    });165 166    it.skipIf(process.platform === 'win32')(167      'does not overwrite existing wrapper',168      () => {169        const libDir = path.join(tempDir, '.local', 'lib');170        const standaloneDir = path.join(libDir, 'qwen-code');171        const binDir = path.join(tempDir, '.local', 'bin');172        fs.mkdirSync(standaloneDir, { recursive: true });173        fs.mkdirSync(binDir, { recursive: true });174 175        const origHome = process.env['HOME'];176        const origShell = process.env['SHELL'];177        process.env['HOME'] = tempDir;178        process.env['SHELL'] = '/bin/zsh';179 180        const wrapperPath = path.join(binDir, 'qwen');181        fs.writeFileSync(wrapperPath, 'existing-content', { mode: 0o755 });182 183        try {184          ensureBinWrapper(standaloneDir, 'linux-x64');185          expect(fs.readFileSync(wrapperPath, 'utf-8')).toBe(186            'existing-content',187          );188        } finally {189          process.env['HOME'] = origHome;190          process.env['SHELL'] = origShell;191        }192      },193    );194  });195 196  describe('performStandaloneUpdate', () => {197    it('rejects invalid version format', async () => {198      const standaloneDir = path.join(tempDir, 'qwen-code');199      fs.mkdirSync(standaloneDir);200      fs.writeFileSync(201        path.join(standaloneDir, 'manifest.json'),202        JSON.stringify({203          name: '@qwen-code/qwen-code',204          target: 'darwin-arm64',205        }),206      );207 208      await expect(209        performStandaloneUpdate(standaloneDir, 'not-a-version'),210      ).rejects.toThrow('Invalid version format');211    });212 213    it('rejects directory without manifest as non-managed install', async () => {214      const standaloneDir = path.join(tempDir, 'qwen-code');215      fs.mkdirSync(standaloneDir);216      // No manifest.json — could be user data217 218      await expect(219        performStandaloneUpdate(standaloneDir, '1.0.0'),220      ).rejects.toThrow('not a Qwen Code standalone install');221    });222 223    it('rejects unknown target in manifest', async () => {224      const standaloneDir = path.join(tempDir, 'qwen-code');225      fs.mkdirSync(standaloneDir);226      fs.writeFileSync(227        path.join(standaloneDir, 'manifest.json'),228        JSON.stringify({229          name: '@qwen-code/qwen-code',230          target: 'freebsd-mips',231        }),232      );233 234      await expect(235        performStandaloneUpdate(standaloneDir, '1.0.0'),236      ).rejects.toThrow('Unknown target');237    });238 239    it('fails gracefully when another update is in progress', async () => {240      const standaloneDir = path.join(tempDir, 'qwen-code');241      const parentDir = path.dirname(standaloneDir);242      fs.mkdirSync(standaloneDir, { recursive: true });243      fs.writeFileSync(244        path.join(standaloneDir, 'manifest.json'),245        JSON.stringify({246          name: '@qwen-code/qwen-code',247          target: 'darwin-arm64',248        }),249      );250 251      // Simulate held lock from a live process (current PID)252      const lockPath = path.join(parentDir, '.qwen-update.lock');253      fs.writeFileSync(lockPath, String(process.pid));254 255      await expect(256        performStandaloneUpdate(standaloneDir, '1.0.0'),257      ).rejects.toThrow('Another update is already in progress');258 259      // Clean up lock260      fs.unlinkSync(lockPath);261    });262  });263 264  describe('isSafeTarEntryPath', () => {265    it('allows double dots inside a filename segment', () => {266      expect(isSafeTarEntryPath('qwen-code/release..notes.md')).toBe(true);267      expect(isSafeTarEntryPath('qwen-code/node/lib/foo..bar')).toBe(true);268      expect(isSafeTarEntryPath('qwen-code/.../file.txt')).toBe(true);269    });270 271    it('rejects parent-directory segments and absolute paths', () => {272      expect(isSafeTarEntryPath('../qwen-code/manifest.json')).toBe(false);273      expect(isSafeTarEntryPath('qwen-code/../manifest.json')).toBe(false);274      expect(isSafeTarEntryPath('qwen-code\\..\\manifest.json')).toBe(false);275      expect(isSafeTarEntryPath('/tmp/qwen-code/manifest.json')).toBe(false);276      expect(isSafeTarEntryPath('C:\\tmp\\qwen-code\\manifest.json')).toBe(277        false,278      );279      expect(isSafeTarEntryPath('')).toBe(false);280    });281  });282 283  describe('rollbackStandaloneUpdate — concurrent lock protection', () => {284    it('returns error when an active update holds the lock', () => {285      const standaloneDir = path.join(tempDir, 'qwen-code');286      const oldDir = `${standaloneDir}.old`;287      const lockPath = path.join(tempDir, '.qwen-update.lock');288      fs.mkdirSync(standaloneDir);289      fs.mkdirSync(oldDir);290      fs.writeFileSync(path.join(standaloneDir, 'manifest.json'), '{}');291      fs.writeFileSync(path.join(oldDir, 'manifest.json'), '{}');292      fs.writeFileSync(lockPath, String(process.pid));293      const result = rollbackStandaloneUpdate(standaloneDir);294      expect(result.ok).toBe(false);295      if (!result.ok) {296        expect(result.detail).toContain('auto-update is currently in progress');297      }298      fs.unlinkSync(lockPath);299    });300 301    it('proceeds when lock has dead PID', () => {302      const standaloneDir = path.join(tempDir, 'qwen-code');303      const oldDir = `${standaloneDir}.old`;304      const lockPath = path.join(tempDir, '.qwen-update.lock');305      fs.mkdirSync(standaloneDir);306      fs.mkdirSync(oldDir);307      fs.writeFileSync(308        path.join(standaloneDir, 'manifest.json'),309        JSON.stringify({ name: '@qwen-code/qwen-code', version: '0.17.0' }),310      );311      fs.writeFileSync(312        path.join(oldDir, 'manifest.json'),313        JSON.stringify({ name: '@qwen-code/qwen-code', version: '0.16.0' }),314      );315      fs.writeFileSync(lockPath, '999999999');316      const result = rollbackStandaloneUpdate(standaloneDir);317      expect(result.ok).toBe(true);318    });319  });320 321  describe.skipIf(process.platform === 'win32')('ensurePathInShellRc', () => {322    it('appends PATH export to zshrc when SHELL is zsh', () => {323      const binDir = path.join(tempDir, 'bin');324      const zshrc = path.join(tempDir, '.zshrc');325      fs.writeFileSync(zshrc, '# existing config\n');326 327      const origShell = process.env['SHELL'];328      const origHome = process.env['HOME'];329      process.env['SHELL'] = '/bin/zsh';330      process.env['HOME'] = tempDir;331 332      try {333        ensurePathInShellRc(binDir);334        const content = fs.readFileSync(zshrc, 'utf-8');335        expect(content).toContain('# Added by Qwen Code standalone installer');336        expect(content).toContain(`export PATH="${binDir}:$PATH"`);337      } finally {338        process.env['SHELL'] = origShell;339        process.env['HOME'] = origHome;340      }341    });342 343    it('skips if marker already in rc file', () => {344      const binDir = path.join(tempDir, 'bin');345      const zshrc = path.join(tempDir, '.zshrc');346      fs.writeFileSync(347        zshrc,348        `# Added by Qwen Code standalone installer\nexport PATH="${binDir}:$PATH"\n`,349      );350 351      const origShell = process.env['SHELL'];352      const origHome = process.env['HOME'];353      process.env['SHELL'] = '/bin/zsh';354      process.env['HOME'] = tempDir;355 356      try {357        ensurePathInShellRc(binDir);358        const content = fs.readFileSync(zshrc, 'utf-8');359        const matches = content.match(360          /# Added by Qwen Code standalone installer/g,361        );362        expect(matches).toHaveLength(1);363      } finally {364        process.env['SHELL'] = origShell;365        process.env['HOME'] = origHome;366      }367    });368 369    it('appends fish_add_path for fish shell', () => {370      const binDir = path.join(tempDir, 'bin');371      const fishDir = path.join(tempDir, '.config', 'fish');372      const fishConfig = path.join(fishDir, 'config.fish');373      fs.mkdirSync(fishDir, { recursive: true });374      fs.writeFileSync(fishConfig, '# existing config\n');375      const origShell = process.env['SHELL'];376      const origHome = process.env['HOME'];377      process.env['SHELL'] = '/usr/bin/fish';378      process.env['HOME'] = tempDir;379      try {380        ensurePathInShellRc(binDir);381        const content = fs.readFileSync(fishConfig, 'utf-8');382        expect(content).toContain('fish_add_path');383        expect(content).toContain(binDir);384      } finally {385        process.env['SHELL'] = origShell;386        process.env['HOME'] = origHome;387      }388    });389 390    it('rejects binDir with shell metacharacters', () => {391      const binDir = path.join(tempDir, 'bin$(evil)');392      const origShell = process.env['SHELL'];393      const origHome = process.env['HOME'];394      process.env['SHELL'] = '/bin/zsh';395      process.env['HOME'] = tempDir;396      try {397        expect(() => ensurePathInShellRc(binDir)).toThrow(398          'unsafe for shell embedding',399        );400      } finally {401        process.env['SHELL'] = origShell;402        process.env['HOME'] = origHome;403      }404    });405 406    it('does nothing for unknown shells', () => {407      const binDir = path.join(tempDir, 'bin');408      const origShell = process.env['SHELL'];409      const origHome = process.env['HOME'];410      process.env['SHELL'] = '/bin/csh';411      process.env['HOME'] = tempDir;412 413      try {414        ensurePathInShellRc(binDir);415        // No rc file should be created416        expect(417          fs.readdirSync(tempDir).filter((f) => f.startsWith('.')),418        ).toHaveLength(0);419      } finally {420        process.env['SHELL'] = origShell;421        process.env['HOME'] = origHome;422      }423    });424  });425});426 
basant307/AI_Governance_Project · CoolFace