CoolFace
Datasetpublic

basant307/AI_Governance_Project

sourceHugging Faceapache-2.0updated 2mo agoView on Hugging Face
0likes48downloads
writeContextFile.test.ts395 linesDownload Raw Back to memory
1/**2 * @license3 * Copyright 2025 Qwen Team4 * SPDX-License-Identifier: Apache-2.05 */6 7import { promises as fs } from 'node:fs';8import * as os from 'node:os';9import * as path from 'node:path';10import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';11import { Storage } from '../config/storage.js';12import {13  AGENT_CONTEXT_FILENAME,14  DEFAULT_CONTEXT_FILENAME,15  MEMORY_SECTION_HEADER,16  setGeminiMdFilename,17} from './const.js';18import { writeWorkspaceContextFile } from './writeContextFile.js';19 20describe('writeWorkspaceContextFile', () => {21  let tmpRoot: string;22  let workspace: string;23  let globalDir: string;24  let getGlobalQwenDirSpy: ReturnType<typeof vi.spyOn>;25 26  beforeEach(async () => {27    tmpRoot = await fs.mkdtemp(path.join(os.tmpdir(), 'qwen-write-context-'));28    workspace = path.join(tmpRoot, 'workspace');29    globalDir = path.join(tmpRoot, 'global');30    await fs.mkdir(workspace, { recursive: true });31    getGlobalQwenDirSpy = vi32      .spyOn(Storage, 'getGlobalQwenDir')33      .mockReturnValue(globalDir);34  });35 36  afterEach(async () => {37    getGlobalQwenDirSpy.mockRestore();38    await fs.rm(tmpRoot, { recursive: true, force: true });39  });40 41  it('creates QWEN.md with a fresh section header on first append', async () => {42    const result = await writeWorkspaceContextFile({43      scope: 'workspace',44      mode: 'append',45      content: '- first entry',46      projectRoot: workspace,47    });48 49    expect(result.filePath).toBe(50      path.join(workspace, DEFAULT_CONTEXT_FILENAME),51    );52    const written = await fs.readFile(result.filePath, 'utf8');53    expect(written).toBe(`${MEMORY_SECTION_HEADER}\n- first entry\n`);54    expect(result.bytesWritten).toBe(Buffer.byteLength(written, 'utf8'));55  });56 57  it('appends under existing section header', async () => {58    const initial = `# project notes\n\n${MEMORY_SECTION_HEADER}\n- first entry\n`;59    const filePath = path.join(workspace, DEFAULT_CONTEXT_FILENAME);60    await fs.writeFile(filePath, initial, 'utf8');61 62    await writeWorkspaceContextFile({63      scope: 'workspace',64      mode: 'append',65      content: '- second entry',66      projectRoot: workspace,67    });68 69    const written = await fs.readFile(filePath, 'utf8');70    expect(written).toBe(71      `# project notes\n\n${MEMORY_SECTION_HEADER}\n- first entry\n- second entry\n`,72    );73  });74 75  it('inserts a section header when file lacks one', async () => {76    const initial = '# project notes\n';77    const filePath = path.join(workspace, DEFAULT_CONTEXT_FILENAME);78    await fs.writeFile(filePath, initial, 'utf8');79 80    await writeWorkspaceContextFile({81      scope: 'workspace',82      mode: 'append',83      content: '- entry',84      projectRoot: workspace,85    });86 87    const written = await fs.readFile(filePath, 'utf8');88    expect(written).toBe(89      `# project notes\n\n${MEMORY_SECTION_HEADER}\n- entry\n`,90    );91  });92 93  it('replaces file contents in replace mode', async () => {94    const filePath = path.join(workspace, DEFAULT_CONTEXT_FILENAME);95    await fs.writeFile(filePath, 'old contents\n', 'utf8');96 97    const result = await writeWorkspaceContextFile({98      scope: 'workspace',99      mode: 'replace',100      content: 'replacement\n',101      projectRoot: workspace,102    });103 104    const written = await fs.readFile(filePath, 'utf8');105    expect(written).toBe('replacement\n');106    expect(result.bytesWritten).toBe(107      Buffer.byteLength('replacement\n', 'utf8'),108    );109  });110 111  it('writes to the global ~/.qwen directory when scope=global', async () => {112    const result = await writeWorkspaceContextFile({113      scope: 'global',114      mode: 'append',115      content: '- global entry',116      projectRoot: workspace,117    });118 119    expect(result.filePath).toBe(120      path.join(globalDir, DEFAULT_CONTEXT_FILENAME),121    );122    expect(getGlobalQwenDirSpy).toHaveBeenCalled();123    const written = await fs.readFile(result.filePath, 'utf8');124    expect(written).toBe(`${MEMORY_SECTION_HEADER}\n- global entry\n`);125  });126 127  it('creates the parent directory when missing', async () => {128    const nested = path.join(workspace, 'nested', 'deep');129    await writeWorkspaceContextFile({130      scope: 'workspace',131      mode: 'append',132      content: '- entry',133      projectRoot: nested,134    });135 136    const created = await fs.readFile(137      path.join(nested, DEFAULT_CONTEXT_FILENAME),138      'utf8',139    );140    expect(created).toContain('- entry');141  });142 143  it('rejects non-absolute projectRoot', async () => {144    await expect(145      writeWorkspaceContextFile({146        scope: 'workspace',147        mode: 'append',148        content: 'x',149        projectRoot: 'relative/path',150      }),151    ).rejects.toThrow(/projectRoot must be absolute/);152  });153 154  it('skips the write entirely when append content is whitespace only', async () => {155    const filePath = path.join(workspace, DEFAULT_CONTEXT_FILENAME);156    await fs.writeFile(filePath, 'preserved\n', 'utf8');157 158    // Spy on `fs.writeFile` rather than relying on filesystem mtime159    // resolution. macOS HFS+ has 1-second mtime resolution; a quick160    // re-write inside the same second would leave `mtimeMs` unchanged161    // and let a regression slip through. The spy makes the162    // "writeFile was never called" invariant explicit and platform-163    // independent.164    const writeFileSpy = vi.spyOn(fs, 'writeFile');165    try {166      const result = await writeWorkspaceContextFile({167        scope: 'workspace',168        mode: 'append',169        content: '\n\n',170        projectRoot: workspace,171      });172 173      const written = await fs.readFile(filePath, 'utf8');174      expect(written).toBe('preserved\n');175      // `bytesWritten: 0` because the no-op short-circuit wrote zero176      // bytes — NOT the existing file size. Earlier revisions returned177      // `stat.size` here, which conflated two semantics and let178      // clients accumulating `sum(bytesWritten)` count the existing179      // file every whitespace POST.180      expect(result.bytesWritten).toBe(0);181      expect(result.changed).toBe(false);182      // The no-op short-circuit must not call writeFile at all.183      expect(writeFileSpy).not.toHaveBeenCalled();184    } finally {185      writeFileSpy.mockRestore();186    }187  });188 189  it('serializes concurrent appends so no entry is lost', async () => {190    // Spawn 10 parallel appends with unique content. Without the191    // per-file mutex, the read-compose-write race in192    // `composeAppendedContent` lets later writes overwrite earlier193    // ones — at least one entry would be missing from the final file.194    const PARALLEL = 10;195    const writes = Array.from({ length: PARALLEL }, (_, i) =>196      writeWorkspaceContextFile({197        scope: 'workspace',198        mode: 'append',199        content: `- entry ${i}`,200        projectRoot: workspace,201      }),202    );203    const results = await Promise.all(writes);204 205    const filePath = path.join(workspace, DEFAULT_CONTEXT_FILENAME);206    const written = await fs.readFile(filePath, 'utf8');207    for (let i = 0; i < PARALLEL; i++) {208      expect(written).toContain(`- entry ${i}`);209    }210    // All N writes report changed; none short-circuited.211    expect(results.every((r) => r.changed)).toBe(true);212    // Exactly one section header — the lock keeps the213    // "is-section-present" check consistent across the group, so we214    // never insert duplicate headers.215    const headerCount = written.split(MEMORY_SECTION_HEADER).length - 1;216    expect(headerCount).toBe(1);217  });218 219  it('marks `changed: false` for a no-op append against a missing file', async () => {220    const result = await writeWorkspaceContextFile({221      scope: 'workspace',222      mode: 'append',223      content: '   ',224      projectRoot: workspace,225    });226    expect(result.changed).toBe(false);227    expect(result.bytesWritten).toBe(0);228    await expect(229      fs.access(path.join(workspace, DEFAULT_CONTEXT_FILENAME)),230    ).rejects.toMatchObject({ code: 'ENOENT' });231  });232 233  it('inserts new entries inside the MEMORY section, not past a later heading', async () => {234    // File where the MEMORY section is followed by other prose.235    // Without the section-boundary fix the new entry would be236    // appended to EOF, landing it inside the `## post` section.237    const filePath = path.join(workspace, DEFAULT_CONTEXT_FILENAME);238    const initial = `# pre\n\n${MEMORY_SECTION_HEADER}\n- first\n\n## post\nstuff\n`;239    await fs.writeFile(filePath, initial, 'utf8');240 241    await writeWorkspaceContextFile({242      scope: 'workspace',243      mode: 'append',244      content: '- second',245      projectRoot: workspace,246    });247 248    const written = await fs.readFile(filePath, 'utf8');249    expect(written).toBe(250      `# pre\n\n${MEMORY_SECTION_HEADER}\n- first\n- second\n\n## post\nstuff\n`,251    );252    // `- second` must be inside the memory block, not after `stuff`.253    const memorySection = written.indexOf(MEMORY_SECTION_HEADER);254    const postSection = written.indexOf('## post');255    const secondIdx = written.indexOf('- second');256    expect(secondIdx).toBeGreaterThan(memorySection);257    expect(secondIdx).toBeLessThan(postSection);258  });259 260  it('does not split a memory entry that contains `## ` inside a fenced code block', async () => {261    // Round-7 [Critical] glm-5.1: the `\n## ` boundary heuristic was262    // matching `## ` lines INSIDE user-authored fenced code blocks263    // (common in QWEN.md memory entries that quote API docs with264    // markdown headings). The old impl would insert the new entry265    // mid-fence, splitting the existing entry. Code-fence-aware266    // detection skips matches inside ``` ``` `` ` blocks.267    const filePath = path.join(workspace, DEFAULT_CONTEXT_FILENAME);268    const fencedEntry = [269      `${MEMORY_SECTION_HEADER}`,270      '- API example:',271      '```markdown',272      '## Request Body',273      'POST /api/thing',274      '```',275      '',276    ].join('\n');277    await fs.writeFile(filePath, fencedEntry, 'utf8');278 279    await writeWorkspaceContextFile({280      scope: 'workspace',281      mode: 'append',282      content: '- next entry',283      projectRoot: workspace,284    });285 286    const written = await fs.readFile(filePath, 'utf8');287    // The new entry must land AFTER the fence, not inside it.288    const fenceClose = written.lastIndexOf('```');289    const newEntry = written.indexOf('- next entry');290    expect(newEntry).toBeGreaterThan(fenceClose);291    // The fenced `## Request Body` must still be intact (no insert292    // before / inside the code block).293    expect(written).toContain(294      '```markdown\n## Request Body\nPOST /api/thing\n```',295    );296  });297 298  it('still respects real `## ` headings outside code fences', async () => {299    const filePath = path.join(workspace, DEFAULT_CONTEXT_FILENAME);300    // Memory section, then a fenced `## ` (must be skipped), then a301    // real `## post` heading (must be honored as the boundary).302    const initial = [303      `${MEMORY_SECTION_HEADER}`,304      '- existing',305      '```',306      '## fake heading inside fence',307      '```',308      '',309      '## post',310      'tail',311      '',312    ].join('\n');313    await fs.writeFile(filePath, initial, 'utf8');314 315    await writeWorkspaceContextFile({316      scope: 'workspace',317      mode: 'append',318      content: '- new',319      projectRoot: workspace,320    });321 322    const written = await fs.readFile(filePath, 'utf8');323    const realPost = written.indexOf('## post');324    const newEntry = written.indexOf('- new');325    expect(newEntry).toBeLessThan(realPost);326    expect(newEntry).toBeGreaterThan(written.indexOf('- existing'));327  });328 329  it('appends to EOF when the MEMORY section is the last block', async () => {330    // Sanity: when no later heading follows, behavior is the331    // pre-fix append-to-end path (still inside the section because332    // the section IS the tail).333    const filePath = path.join(workspace, DEFAULT_CONTEXT_FILENAME);334    const initial = `# pre\n\n${MEMORY_SECTION_HEADER}\n- a\n`;335    await fs.writeFile(filePath, initial, 'utf8');336 337    await writeWorkspaceContextFile({338      scope: 'workspace',339      mode: 'append',340      content: '- b',341      projectRoot: workspace,342    });343 344    const written = await fs.readFile(filePath, 'utf8');345    expect(written).toBe(`# pre\n\n${MEMORY_SECTION_HEADER}\n- a\n- b\n`);346  });347 348  it('does not create the parent directory on a no-op append', async () => {349    // Whitespace-only append targeting a non-existent nested path350    // must NOT call fs.mkdir — the no-op detection short-circuits351    // BEFORE acquiring the lock or touching the filesystem. Without352    // this, an empty POST would still bump the parent directory's353    // mtime even though the helper reports `changed: false`.354    const nested = path.join(workspace, 'never-exists');355    const result = await writeWorkspaceContextFile({356      scope: 'workspace',357      mode: 'append',358      content: '\n\n',359      projectRoot: nested,360    });361    expect(result.changed).toBe(false);362    await expect(fs.access(nested)).rejects.toMatchObject({ code: 'ENOENT' });363  });364 365  it('honors setGeminiMdFilename overrides so POST targets the same file GET surfaces', async () => {366    // Round-trip the `setGeminiMdFilename` override: with the prior367    // `DEFAULT_CONTEXT_FILENAME` hard-code, a deployment that switched368    // the context filename to `AGENTS.md` saw GET list the new file369    // but POST keep writing to `QWEN.md`. The fix routes370    // `resolveContextFilePath` through `getCurrentGeminiMdFilename()`371    // so both surfaces agree.372    try {373      setGeminiMdFilename(AGENT_CONTEXT_FILENAME);374      const result = await writeWorkspaceContextFile({375        scope: 'workspace',376        mode: 'append',377        content: '- entry',378        projectRoot: workspace,379      });380      expect(result.filePath).toBe(381        path.join(workspace, AGENT_CONTEXT_FILENAME),382      );383      const written = await fs.readFile(result.filePath, 'utf8');384      expect(written).toContain('- entry');385      // The legacy QWEN.md must NOT have been written — the prior386      // hard-coded behavior would have created it here.387      await expect(388        fs.access(path.join(workspace, DEFAULT_CONTEXT_FILENAME)),389      ).rejects.toMatchObject({ code: 'ENOENT' });390    } finally {391      setGeminiMdFilename(DEFAULT_CONTEXT_FILENAME);392    }393  });394});395 
basant307/AI_Governance_Project · CoolFace