Skip to content
项目
群组
代码片段
帮助
当前项目
正在载入...
登录 / 注册
切换导航面板
B
bit-pm
项目
项目
详情
活动
周期分析
仓库
仓库
文件
提交
分支
标签
贡献者
图表
比较
统计图
议题
0
议题
0
列表
看板
标记
里程碑
合并请求
0
合并请求
0
CI / CD
CI / CD
流水线
作业
日程
统计图
Wiki
Wiki
代码片段
代码片段
成员
成员
折叠边栏
关闭边栏
活动
图像
聊天
创建新问题
作业
提交
问题看板
Open sidebar
燕伟桐
bit-pm
Commits
d57c6e24
Unverified
提交
d57c6e24
authored
7月 09, 2025
作者:
Will Chen
提交者:
GitHub
7月 09, 2025
浏览文件
操作
浏览文件
下载
电子邮件补丁
差异文件
Add readSettings unit test (#602)
上级
4d7d9ec2
隐藏空白字符变更
内嵌
并排
正在显示
1 个修改的文件
包含
431 行增加
和
0 行删除
+431
-0
readSettings.test.ts
src/__tests__/readSettings.test.ts
+431
-0
没有找到文件。
src/__tests__/readSettings.test.ts
0 → 100644
浏览文件 @
d57c6e24
import
{
describe
,
it
,
expect
,
vi
,
beforeEach
,
afterEach
}
from
"vitest"
;
import
fs
from
"node:fs"
;
import
path
from
"node:path"
;
import
{
safeStorage
}
from
"electron"
;
import
{
readSettings
,
getSettingsFilePath
}
from
"@/main/settings"
;
import
{
getUserDataPath
}
from
"@/paths/paths"
;
// Mock dependencies
vi
.
mock
(
"node:fs"
);
vi
.
mock
(
"node:path"
);
vi
.
mock
(
"electron"
,
()
=>
({
safeStorage
:
{
isEncryptionAvailable
:
vi
.
fn
(),
decryptString
:
vi
.
fn
(),
},
}));
vi
.
mock
(
"@/paths/paths"
,
()
=>
({
getUserDataPath
:
vi
.
fn
(),
}));
const
mockFs
=
vi
.
mocked
(
fs
);
const
mockPath
=
vi
.
mocked
(
path
);
const
mockSafeStorage
=
vi
.
mocked
(
safeStorage
);
const
mockGetUserDataPath
=
vi
.
mocked
(
getUserDataPath
);
describe
(
"readSettings"
,
()
=>
{
const
mockUserDataPath
=
"/mock/user/data"
;
const
mockSettingsPath
=
"/mock/user/data/user-settings.json"
;
beforeEach
(()
=>
{
vi
.
clearAllMocks
();
mockGetUserDataPath
.
mockReturnValue
(
mockUserDataPath
);
mockPath
.
join
.
mockReturnValue
(
mockSettingsPath
);
mockSafeStorage
.
isEncryptionAvailable
.
mockReturnValue
(
true
);
});
afterEach
(()
=>
{
vi
.
restoreAllMocks
();
});
describe
(
"when settings file does not exist"
,
()
=>
{
it
(
"should create default settings file and return default settings"
,
()
=>
{
mockFs
.
existsSync
.
mockReturnValue
(
false
);
mockFs
.
writeFileSync
.
mockImplementation
(()
=>
{});
const
result
=
readSettings
();
expect
(
mockFs
.
existsSync
).
toHaveBeenCalledWith
(
mockSettingsPath
);
expect
(
mockFs
.
writeFileSync
).
toHaveBeenCalledWith
(
mockSettingsPath
,
expect
.
stringContaining
(
'"selectedModel"'
),
);
expect
(
result
).
toEqual
({
selectedModel
:
{
name
:
"auto"
,
provider
:
"auto"
,
},
providerSettings
:
{},
telemetryConsent
:
"unset"
,
telemetryUserId
:
expect
.
any
(
String
),
hasRunBefore
:
false
,
experiments
:
{},
enableProLazyEditsMode
:
true
,
enableProSmartFilesContextMode
:
true
,
selectedChatMode
:
"build"
,
enableAutoFixProblems
:
false
,
enableAutoUpdate
:
true
,
releaseChannel
:
"stable"
,
});
});
});
describe
(
"when settings file exists"
,
()
=>
{
it
(
"should read and merge settings with defaults"
,
()
=>
{
const
mockFileContent
=
{
selectedModel
:
{
name
:
"gpt-4"
,
provider
:
"openai"
,
},
telemetryConsent
:
"opted_in"
,
hasRunBefore
:
true
,
};
mockFs
.
existsSync
.
mockReturnValue
(
true
);
mockFs
.
readFileSync
.
mockReturnValue
(
JSON
.
stringify
(
mockFileContent
));
const
result
=
readSettings
();
expect
(
mockFs
.
readFileSync
).
toHaveBeenCalledWith
(
mockSettingsPath
,
"utf-8"
,
);
expect
(
result
.
selectedModel
).
toEqual
({
name
:
"gpt-4"
,
provider
:
"openai"
,
});
expect
(
result
.
telemetryConsent
).
toBe
(
"opted_in"
);
expect
(
result
.
hasRunBefore
).
toBe
(
true
);
// Should still have defaults for missing properties
expect
(
result
.
enableAutoUpdate
).
toBe
(
true
);
expect
(
result
.
releaseChannel
).
toBe
(
"stable"
);
});
it
(
"should decrypt encrypted provider API keys"
,
()
=>
{
const
mockFileContent
=
{
providerSettings
:
{
openai
:
{
apiKey
:
{
value
:
"encrypted-api-key"
,
encryptionType
:
"electron-safe-storage"
,
},
},
},
};
mockFs
.
existsSync
.
mockReturnValue
(
true
);
mockFs
.
readFileSync
.
mockReturnValue
(
JSON
.
stringify
(
mockFileContent
));
mockSafeStorage
.
decryptString
.
mockReturnValue
(
"decrypted-api-key"
);
const
result
=
readSettings
();
expect
(
mockSafeStorage
.
decryptString
).
toHaveBeenCalledWith
(
Buffer
.
from
(
"encrypted-api-key"
,
"base64"
),
);
expect
(
result
.
providerSettings
.
openai
.
apiKey
).
toEqual
({
value
:
"decrypted-api-key"
,
encryptionType
:
"electron-safe-storage"
,
});
});
it
(
"should decrypt encrypted GitHub access token"
,
()
=>
{
const
mockFileContent
=
{
githubAccessToken
:
{
value
:
"encrypted-github-token"
,
encryptionType
:
"electron-safe-storage"
,
},
};
mockFs
.
existsSync
.
mockReturnValue
(
true
);
mockFs
.
readFileSync
.
mockReturnValue
(
JSON
.
stringify
(
mockFileContent
));
mockSafeStorage
.
decryptString
.
mockReturnValue
(
"decrypted-github-token"
);
const
result
=
readSettings
();
expect
(
mockSafeStorage
.
decryptString
).
toHaveBeenCalledWith
(
Buffer
.
from
(
"encrypted-github-token"
,
"base64"
),
);
expect
(
result
.
githubAccessToken
).
toEqual
({
value
:
"decrypted-github-token"
,
encryptionType
:
"electron-safe-storage"
,
});
});
it
(
"should decrypt encrypted Supabase tokens"
,
()
=>
{
const
mockFileContent
=
{
supabase
:
{
accessToken
:
{
value
:
"encrypted-access-token"
,
encryptionType
:
"electron-safe-storage"
,
},
refreshToken
:
{
value
:
"encrypted-refresh-token"
,
encryptionType
:
"electron-safe-storage"
,
},
},
};
mockFs
.
existsSync
.
mockReturnValue
(
true
);
mockFs
.
readFileSync
.
mockReturnValue
(
JSON
.
stringify
(
mockFileContent
));
mockSafeStorage
.
decryptString
.
mockReturnValueOnce
(
"decrypted-refresh-token"
)
.
mockReturnValueOnce
(
"decrypted-access-token"
);
const
result
=
readSettings
();
expect
(
mockSafeStorage
.
decryptString
).
toHaveBeenCalledTimes
(
2
);
expect
(
result
.
supabase
?.
refreshToken
).
toEqual
({
value
:
"decrypted-refresh-token"
,
encryptionType
:
"electron-safe-storage"
,
});
expect
(
result
.
supabase
?.
accessToken
).
toEqual
({
value
:
"decrypted-access-token"
,
encryptionType
:
"electron-safe-storage"
,
});
});
it
(
"should handle plaintext secrets without decryption"
,
()
=>
{
const
mockFileContent
=
{
githubAccessToken
:
{
value
:
"plaintext-token"
,
encryptionType
:
"plaintext"
,
},
providerSettings
:
{
openai
:
{
apiKey
:
{
value
:
"plaintext-api-key"
,
encryptionType
:
"plaintext"
,
},
},
},
};
mockFs
.
existsSync
.
mockReturnValue
(
true
);
mockFs
.
readFileSync
.
mockReturnValue
(
JSON
.
stringify
(
mockFileContent
));
const
result
=
readSettings
();
expect
(
mockSafeStorage
.
decryptString
).
not
.
toHaveBeenCalled
();
expect
(
result
.
githubAccessToken
?.
value
).
toBe
(
"plaintext-token"
);
expect
(
result
.
providerSettings
.
openai
.
apiKey
?.
value
).
toBe
(
"plaintext-api-key"
,
);
});
it
(
"should handle secrets without encryptionType"
,
()
=>
{
const
mockFileContent
=
{
githubAccessToken
:
{
value
:
"token-without-encryption-type"
,
},
providerSettings
:
{
openai
:
{
apiKey
:
{
value
:
"api-key-without-encryption-type"
,
},
},
},
};
mockFs
.
existsSync
.
mockReturnValue
(
true
);
mockFs
.
readFileSync
.
mockReturnValue
(
JSON
.
stringify
(
mockFileContent
));
const
result
=
readSettings
();
expect
(
mockSafeStorage
.
decryptString
).
not
.
toHaveBeenCalled
();
expect
(
result
.
githubAccessToken
?.
value
).
toBe
(
"token-without-encryption-type"
,
);
expect
(
result
.
providerSettings
.
openai
.
apiKey
?.
value
).
toBe
(
"api-key-without-encryption-type"
,
);
});
it
(
"should strip extra fields not recognized by the schema"
,
()
=>
{
const
mockFileContent
=
{
selectedModel
:
{
name
:
"gpt-4"
,
provider
:
"openai"
,
},
telemetryConsent
:
"opted_in"
,
hasRunBefore
:
true
,
// Extra fields that are not in the schema
unknownField
:
"should be removed"
,
deprecatedSetting
:
true
,
extraConfig
:
{
someValue
:
123
,
anotherValue
:
"test"
,
},
};
mockFs
.
existsSync
.
mockReturnValue
(
true
);
mockFs
.
readFileSync
.
mockReturnValue
(
JSON
.
stringify
(
mockFileContent
));
const
result
=
readSettings
();
expect
(
mockFs
.
readFileSync
).
toHaveBeenCalledWith
(
mockSettingsPath
,
"utf-8"
,
);
expect
(
result
.
selectedModel
).
toEqual
({
name
:
"gpt-4"
,
provider
:
"openai"
,
});
expect
(
result
.
telemetryConsent
).
toBe
(
"opted_in"
);
expect
(
result
.
hasRunBefore
).
toBe
(
true
);
// Extra fields should be stripped by schema validation
expect
(
result
).
not
.
toHaveProperty
(
"unknownField"
);
expect
(
result
).
not
.
toHaveProperty
(
"deprecatedSetting"
);
expect
(
result
).
not
.
toHaveProperty
(
"extraConfig"
);
// Should still have defaults for missing properties
expect
(
result
.
enableAutoUpdate
).
toBe
(
true
);
expect
(
result
.
releaseChannel
).
toBe
(
"stable"
);
});
});
describe
(
"error handling"
,
()
=>
{
it
(
"should return default settings when file read fails"
,
()
=>
{
mockFs
.
existsSync
.
mockReturnValue
(
true
);
mockFs
.
readFileSync
.
mockImplementation
(()
=>
{
throw
new
Error
(
"File read error"
);
});
const
consoleSpy
=
vi
.
spyOn
(
console
,
"error"
)
.
mockImplementation
(()
=>
{});
const
result
=
readSettings
();
expect
(
consoleSpy
).
toHaveBeenCalledWith
(
"Error reading settings:"
,
expect
.
any
(
Error
),
);
expect
(
result
).
toEqual
({
selectedModel
:
{
name
:
"auto"
,
provider
:
"auto"
,
},
providerSettings
:
{},
telemetryConsent
:
"unset"
,
telemetryUserId
:
expect
.
any
(
String
),
hasRunBefore
:
false
,
experiments
:
{},
enableProLazyEditsMode
:
true
,
enableProSmartFilesContextMode
:
true
,
selectedChatMode
:
"build"
,
enableAutoFixProblems
:
false
,
enableAutoUpdate
:
true
,
releaseChannel
:
"stable"
,
});
consoleSpy
.
mockRestore
();
});
it
(
"should return default settings when JSON parsing fails"
,
()
=>
{
mockFs
.
existsSync
.
mockReturnValue
(
true
);
mockFs
.
readFileSync
.
mockReturnValue
(
"invalid json"
);
const
consoleSpy
=
vi
.
spyOn
(
console
,
"error"
)
.
mockImplementation
(()
=>
{});
const
result
=
readSettings
();
expect
(
consoleSpy
).
toHaveBeenCalledWith
(
"Error reading settings:"
,
expect
.
any
(
Error
),
);
expect
(
result
).
toMatchObject
({
selectedModel
:
{
name
:
"auto"
,
provider
:
"auto"
,
},
releaseChannel
:
"stable"
,
});
consoleSpy
.
mockRestore
();
});
it
(
"should return default settings when schema validation fails"
,
()
=>
{
const
mockFileContent
=
{
selectedModel
:
{
name
:
"gpt-4"
,
// Missing required 'provider' field
},
releaseChannel
:
"invalid-channel"
,
// Invalid enum value
};
mockFs
.
existsSync
.
mockReturnValue
(
true
);
mockFs
.
readFileSync
.
mockReturnValue
(
JSON
.
stringify
(
mockFileContent
));
const
consoleSpy
=
vi
.
spyOn
(
console
,
"error"
)
.
mockImplementation
(()
=>
{});
const
result
=
readSettings
();
expect
(
consoleSpy
).
toHaveBeenCalledWith
(
"Error reading settings:"
,
expect
.
any
(
Error
),
);
expect
(
result
).
toMatchObject
({
selectedModel
:
{
name
:
"auto"
,
provider
:
"auto"
,
},
releaseChannel
:
"stable"
,
});
consoleSpy
.
mockRestore
();
});
it
(
"should handle decryption errors gracefully"
,
()
=>
{
const
mockFileContent
=
{
githubAccessToken
:
{
value
:
"corrupted-encrypted-data"
,
encryptionType
:
"electron-safe-storage"
,
},
};
mockFs
.
existsSync
.
mockReturnValue
(
true
);
mockFs
.
readFileSync
.
mockReturnValue
(
JSON
.
stringify
(
mockFileContent
));
mockSafeStorage
.
decryptString
.
mockImplementation
(()
=>
{
throw
new
Error
(
"Decryption failed"
);
});
const
consoleSpy
=
vi
.
spyOn
(
console
,
"error"
)
.
mockImplementation
(()
=>
{});
const
result
=
readSettings
();
expect
(
consoleSpy
).
toHaveBeenCalledWith
(
"Error reading settings:"
,
expect
.
any
(
Error
),
);
expect
(
result
).
toMatchObject
({
selectedModel
:
{
name
:
"auto"
,
provider
:
"auto"
,
},
releaseChannel
:
"stable"
,
});
consoleSpy
.
mockRestore
();
});
});
describe
(
"getSettingsFilePath"
,
()
=>
{
it
(
"should return correct settings file path"
,
()
=>
{
const
result
=
getSettingsFilePath
();
expect
(
mockGetUserDataPath
).
toHaveBeenCalled
();
expect
(
mockPath
.
join
).
toHaveBeenCalledWith
(
mockUserDataPath
,
"user-settings.json"
,
);
expect
(
result
).
toBe
(
mockSettingsPath
);
});
});
});
编写
预览
Markdown
格式
0%
重试
或
添加新文件
添加附件
取消
您添加了
0
人
到此讨论。请谨慎行事。
请先完成此评论的编辑!
取消
请
注册
或者
登录
后发表评论