CoolFace
Datasetpublic

basant307/AI_Governance_Project

sourceHugging Faceapache-2.0updated 2mo agoView on Hugging Face
0likes48downloads
loadedSettingsAdapter.test.ts187 linesDownload Raw Back to config
1/**2 * @license3 * Copyright 2025 Qwen Team4 * SPDX-License-Identifier: Apache-2.05 */6 7import { describe, expect, it, vi } from 'vitest';8import { createLoadedSettingsAdapter } from './loadedSettingsAdapter.js';9import { SettingScope } from './settings.js';10 11// settingsUtils makes real fs calls in backup/restore — stub them out so the12// tests can focus on adapter behavior without touching disk.13vi.mock('../utils/settingsUtils.js', async (importOriginal) => {14  const actual =15    await importOriginal<typeof import('../utils/settingsUtils.js')>();16  return {17    ...actual,18    backupSettingsFile: vi.fn(),19    restoreSettingsFromBackup: vi.fn(),20    cleanupSettingsBackup: vi.fn(),21  };22});23 24// Named shape so dot-access on the known keys (`env`, `modelProviders`) is not25// treated as access through an index signature — keeps the strict TS option26// `noPropertyAccessFromIndexSignature` happy while still allowing arbitrary27// extra keys via the index signature.28interface SettingsShape {29  env?: Record<string, unknown>;30  modelProviders?: Record<string, unknown>;31  [key: string]: unknown;32}33 34interface MutableSettingsFile {35  settings: SettingsShape;36  originalSettings: SettingsShape;37  path: string;38}39 40function makeSettings(initial: SettingsShape = {}) {41  const file: MutableSettingsFile = {42    settings: structuredClone(initial),43    originalSettings: structuredClone(initial),44    path: '/tmp/qwen-test-settings.json',45  };46  const setValue = vi.fn(47    (_scope: SettingScope, key: string, value: unknown) => {48      const parts = key.split('.');49      let current: Record<string, unknown> = file.settings as Record<50        string,51        unknown52      >;53      for (let i = 0; i < parts.length; i++) {54        const part = parts[i]!;55        // Mirror setNestedPropertySafe's reserved-segment rejection. Inline56        // literal === comparisons (rather than e.g. Set.has) are what57        // CodeQL's prototype-pollution sanitiser recognises, so we use them58        // at the only step that actually writes to `current`.59        if (60          part === '__proto__' ||61          part === 'constructor' ||62          part === 'prototype'63        ) {64          throw new Error(`mock setValue refused reserved segment in: ${key}`);65        }66        if (i === parts.length - 1) {67          current[part] = value;68        } else {69          if (!current[part] || typeof current[part] !== 'object') {70            current[part] = {};71          }72          current = current[part] as Record<string, unknown>;73        }74      }75      file.originalSettings = structuredClone(file.settings);76    },77  );78  const recomputeMerged = vi.fn(() => {79    /* merged() is computed lazily via the getter below */80  });81  const settings = {82    get merged() {83      return file.settings;84    },85    forScope: vi.fn(() => file),86    setValue,87    recomputeMerged,88  };89  return { settings, file, setValue, recomputeMerged };90}91 92describe('createLoadedSettingsAdapter', () => {93  it('forwards setValue to LoadedSettings.setValue with the resolved scope', () => {94    const { settings, setValue } = makeSettings();95    const adapter = createLoadedSettingsAdapter(96      settings as never,97      SettingScope.User,98    );99    adapter.setValue('env.MY_KEY', 'val');100    expect(setValue).toHaveBeenCalledWith(101      SettingScope.User,102      'env.MY_KEY',103      'val',104    );105  });106 107  it('rejects prototype-pollution keys before reaching LoadedSettings', () => {108    const { settings, setValue } = makeSettings();109    const adapter = createLoadedSettingsAdapter(110      settings as never,111      SettingScope.User,112    );113    expect(() => adapter.setValue('__proto__.polluted', 'x')).toThrow(114      /reserved segment/,115    );116    expect(() => adapter.setValue('foo.constructor.bar', 'x')).toThrow(117      /reserved segment/,118    );119    expect(() => adapter.setValue('prototype.x', 'x')).toThrow(120      /reserved segment/,121    );122    // The guard short-circuits before delegating to LoadedSettings — that's the123    // contract this test exists to lock in.124    expect(setValue).not.toHaveBeenCalled();125  });126 127  it('getValue reads from settings.merged via dotted key', () => {128    const { settings } = makeSettings({129      env: { MY_KEY: 'from-merged' },130      modelProviders: { openai: [{ id: 'gpt' }] },131    });132    const adapter = createLoadedSettingsAdapter(133      settings as never,134      SettingScope.User,135    );136    expect(adapter.getValue('env.MY_KEY')).toBe('from-merged');137    expect(adapter.getValue('modelProviders.openai')).toEqual([{ id: 'gpt' }]);138    expect(adapter.getValue('missing.path')).toBeUndefined();139  });140 141  it('backup() snapshots in-memory state; restore() reverts and recomputes merged', () => {142    const { settings, file, recomputeMerged } = makeSettings({143      env: { ORIGINAL: '1' },144    });145    const adapter = createLoadedSettingsAdapter(146      settings as never,147      SettingScope.User,148    );149 150    // backup/restore/cleanupBackup are optional in the contract, but151    // createLoadedSettingsAdapter always installs them — assert + use !.152    expect(adapter.backup).toBeTypeOf('function');153    adapter.backup!();154 155    // Simulate mutations that would happen during an install plan apply.156    adapter.setValue('env.NEW_KEY', 'new-value');157    expect(file.settings.env).toEqual({158      ORIGINAL: '1',159      NEW_KEY: 'new-value',160    });161 162    expect(adapter.restore).toBeTypeOf('function');163    adapter.restore!();164 165    expect(file.settings).toEqual({ env: { ORIGINAL: '1' } });166    expect(file.originalSettings).toEqual({ env: { ORIGINAL: '1' } });167    expect(recomputeMerged).toHaveBeenCalled();168  });169 170  it('cleanupBackup() clears the in-memory snapshot so a later restore is a no-op', () => {171    const { settings, file } = makeSettings({ env: { K: 'v1' } });172    const adapter = createLoadedSettingsAdapter(173      settings as never,174      SettingScope.User,175    );176    expect(adapter.backup).toBeTypeOf('function');177    adapter.backup!();178    adapter.setValue('env.K', 'v2');179    expect(adapter.cleanupBackup).toBeTypeOf('function');180    adapter.cleanupBackup!();181    // restore after cleanup should not bring v1 back182    expect(adapter.restore).toBeTypeOf('function');183    adapter.restore!();184    expect(file.settings.env).toEqual({ K: 'v2' });185  });186});187 
basant307/AI_Governance_Project · CoolFace