File size: 3,595 Bytes
50720ce
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
119
120
121
122
123
124
125
126
127
/**
 * Tests that update-checker writes version state to data/ (gitignored),
 * NOT to config/default.yaml (git-tracked).
 */

import { describe, it, expect, beforeEach, vi } from "vitest";

const mockWriteFileSync = vi.fn();
const mockExistsSync = vi.fn(() => false);
const mockMkdirSync = vi.fn();
const mockReadFileSync = vi.fn();
const mockMutateClientConfig = vi.fn();

vi.mock("../config.js", () => ({
  mutateClientConfig: mockMutateClientConfig,
  reloadAllConfigs: vi.fn(),
}));

vi.mock("../paths.js", () => ({
  getConfigDir: vi.fn(() => "/fake/config"),
  getDataDir: vi.fn(() => "/fake/data"),
  isEmbedded: vi.fn(() => false),
}));

vi.mock("../utils/jitter.js", () => ({
  jitterInt: vi.fn((ms: number) => ms),
}));

vi.mock("../tls/curl-fetch.js", () => ({
  curlFetchGet: vi.fn(),
}));

vi.mock("fs", async (importOriginal) => {
  const actual = await importOriginal<typeof import("fs")>();
  return {
    ...actual,
    readFileSync: mockReadFileSync,
    writeFileSync: mockWriteFileSync,
    existsSync: mockExistsSync,
    mkdirSync: mockMkdirSync,
  };
});

vi.mock("js-yaml", () => ({
  default: {
    load: vi.fn(() => ({
      client: { app_version: "1.0.0", build_number: "100" },
    })),
  },
}));

import { curlFetchGet } from "../tls/curl-fetch.js";

const APPCAST_XML = `<?xml version="1.0"?>
<rss><channel><item>
  <enclosure sparkle:shortVersionString="2.0.0" sparkle:version="200" url="https://example.com/download"/>
</item></channel></rss>`;

describe("update-checker writes to data/, not config/", () => {
  beforeEach(() => {
    vi.clearAllMocks();
    mockExistsSync.mockReturnValue(false);
    mockReadFileSync.mockReturnValue("");
  });

  it("applyVersionUpdate writes to data/version-state.json, not config/default.yaml", async () => {
    vi.mocked(curlFetchGet).mockResolvedValue({
      ok: true,
      status: 200,
      body: APPCAST_XML,
    });

    // Dynamic import to get fresh module state
    const { checkForUpdate } = await import("../update-checker.js");
    await checkForUpdate();

    // Should write version-state.json to data/
    const versionWrites = mockWriteFileSync.mock.calls.filter(
      (call) => (call[0] as string).includes("version-state.json"),
    );
    expect(versionWrites.length).toBeGreaterThanOrEqual(1);
    const writePath = versionWrites[0][0] as string;
    expect(writePath).toBe("/fake/data/version-state.json");

    // Parse the written content
    const written = JSON.parse(versionWrites[0][1] as string) as {
      app_version: string;
      build_number: string;
    };
    expect(written.app_version).toBe("2.0.0");
    expect(written.build_number).toBe("200");
  });

  it("never calls mutateYaml on config/default.yaml", async () => {
    vi.mocked(curlFetchGet).mockResolvedValue({
      ok: true,
      status: 200,
      body: APPCAST_XML,
    });

    const { checkForUpdate } = await import("../update-checker.js");
    await checkForUpdate();

    // No writes should target config/default.yaml
    const configWrites = mockWriteFileSync.mock.calls.filter(
      (call) => (call[0] as string).includes("/fake/config/"),
    );
    expect(configWrites).toHaveLength(0);
  });

  it("updates runtime config via mutateClientConfig", async () => {
    vi.mocked(curlFetchGet).mockResolvedValue({
      ok: true,
      status: 200,
      body: APPCAST_XML,
    });

    const { checkForUpdate } = await import("../update-checker.js");
    await checkForUpdate();

    expect(mockMutateClientConfig).toHaveBeenCalledWith({
      app_version: "2.0.0",
      build_number: "200",
    });
  });
});