File size: 3,753 Bytes
3201ca6
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
128
129
130
131
132
133
134
135
136
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";

import type { Backend } from "#/api/backend-registry/types";
import {
  __resetActiveStoreForTests,
  setActiveSelection,
  setRegisteredBackends,
} from "#/api/backend-registry/active-store";
import { SecretsService } from "#/api/secrets-service";

const { mockListSecrets, mockGetSecret, mockUpsertSecret, mockDeleteSecret } =
  vi.hoisted(() => ({
    mockListSecrets: vi.fn(),
    mockGetSecret: vi.fn(),
    mockUpsertSecret: vi.fn(),
    mockDeleteSecret: vi.fn(),
  }));

vi.mock("@openhands/typescript-client/clients", () => ({
  SettingsClient: vi.fn(function SettingsClientMock() {
    return {
      listSecrets: mockListSecrets,
      getSecret: mockGetSecret,
      upsertSecret: mockUpsertSecret,
      deleteSecret: mockDeleteSecret,
    };
  }),
}));

const localBackend: Backend = {
  id: "local",
  name: "Local",
  host: "http://127.0.0.1:8000",
  apiKey: "",
  kind: "local",
};

describe("SecretsService", () => {
  beforeEach(() => {
    window.localStorage.clear();
    __resetActiveStoreForTests();
    setRegisteredBackends([localBackend]);
    setActiveSelection({ backendId: localBackend.id });
    mockListSecrets.mockReset();
    mockGetSecret.mockReset();
    mockUpsertSecret.mockReset();
    mockDeleteSecret.mockReset();
  });

  afterEach(() => {
    __resetActiveStoreForTests();
  });

  it("lists local secret metadata through the strict read", async () => {
    mockListSecrets.mockResolvedValue({
      secrets: [{ name: "OPENHANDS_URL", description: "Canvas origin" }],
    });

    await expect(SecretsService.getSecretsOrThrow()).resolves.toEqual([
      { name: "OPENHANDS_URL", description: "Canvas origin" },
    ]);
    expect(mockListSecrets).toHaveBeenCalledTimes(1);
  });

  it("preserves the existing value when updating description", async () => {
    // Arrange
    mockGetSecret.mockResolvedValue("keep-this");
    mockUpsertSecret.mockResolvedValue({
      name: "OpenAI_API_Key",
      description: "Updated description",
    });

    // Act
    await SecretsService.updateSecret(
      "OpenAI_API_Key",
      "OpenAI_API_Key",
      "Updated description",
    );

    // Assert
    expect(mockGetSecret).toHaveBeenCalledWith("OpenAI_API_Key");
    expect(mockUpsertSecret).toHaveBeenCalledWith({
      name: "OpenAI_API_Key",
      value: "keep-this",
      description: "Updated description",
    });
    expect(mockDeleteSecret).not.toHaveBeenCalled();
  });

  it("overwrites the value without reading the existing one", async () => {
    // Arrange
    mockUpsertSecret.mockResolvedValue({
      name: "OpenAI_API_Key",
      description: "Demo secret",
    });

    // Act
    await SecretsService.updateSecret(
      "OpenAI_API_Key",
      "OpenAI_API_Key",
      "Demo secret",
      "new-value",
    );

    // Assert
    expect(mockGetSecret).not.toHaveBeenCalled();
    expect(mockUpsertSecret).toHaveBeenCalledWith({
      name: "OpenAI_API_Key",
      value: "new-value",
      description: "Demo secret",
    });
  });

  it("renames secrets by re-upserting and removing the old entry", async () => {
    // Arrange
    mockGetSecret.mockResolvedValue("original-value");
    mockUpsertSecret.mockResolvedValue({
      name: "New_Key",
      description: "Renamed",
    });
    mockDeleteSecret.mockResolvedValue({ deleted: true });

    // Act
    await SecretsService.updateSecret("Old_Key", "New_Key", "Renamed");

    // Assert
    expect(mockGetSecret).toHaveBeenCalledWith("Old_Key");
    expect(mockUpsertSecret).toHaveBeenCalledWith({
      name: "New_Key",
      value: "original-value",
      description: "Renamed",
    });
    expect(mockDeleteSecret).toHaveBeenCalledWith("Old_Key");
  });
});