fix: clean up deleted provider state correctly (#696)

This commit is contained in:
Lingxuan Zuo
2026-03-27 23:32:56 +08:00
committed by GitHub
Unverified
parent 07f3c310b5
commit 9b56d80d22
8 changed files with 510 additions and 32 deletions

View File

@@ -36,6 +36,11 @@ async function writeOpenClawJson(config: unknown): Promise<void> {
await writeFile(join(openclawDir, 'openclaw.json'), JSON.stringify(config, null, 2), 'utf8');
}
async function readOpenClawJson(): Promise<Record<string, unknown>> {
const content = await readFile(join(testHome, '.openclaw', 'openclaw.json'), 'utf8');
return JSON.parse(content) as Record<string, unknown>;
}
async function readAuthProfiles(agentId: string): Promise<Record<string, unknown>> {
const content = await readFile(join(testHome, '.openclaw', 'agents', agentId, 'agent', 'auth-profiles.json'), 'utf8');
return JSON.parse(content) as Record<string, unknown>;
@@ -118,6 +123,188 @@ describe('saveProviderKeyToOpenClaw', () => {
});
});
describe('removeProviderKeyFromOpenClaw', () => {
beforeEach(async () => {
vi.resetModules();
vi.restoreAllMocks();
await rm(testHome, { recursive: true, force: true });
await rm(testUserData, { recursive: true, force: true });
});
it('removes only the default api-key profile for a provider', async () => {
await writeAgentAuthProfiles('main', {
version: 1,
profiles: {
'custom-abc12345:default': {
type: 'api_key',
provider: 'custom-abc12345',
key: 'sk-main',
},
'custom-abc12345:backup': {
type: 'api_key',
provider: 'custom-abc12345',
key: 'sk-backup',
},
},
order: {
'custom-abc12345': [
'custom-abc12345:default',
'custom-abc12345:backup',
],
},
lastGood: {
'custom-abc12345': 'custom-abc12345:default',
},
});
const { removeProviderKeyFromOpenClaw } = await import('@electron/utils/openclaw-auth');
await removeProviderKeyFromOpenClaw('custom-abc12345', 'main');
const mainProfiles = await readAuthProfiles('main');
expect(mainProfiles.profiles).toEqual({
'custom-abc12345:backup': {
type: 'api_key',
provider: 'custom-abc12345',
key: 'sk-backup',
},
});
expect(mainProfiles.order).toEqual({
'custom-abc12345': ['custom-abc12345:backup'],
});
expect(mainProfiles.lastGood).toEqual({});
});
it('cleans stale default-profile references even when the profile object is already missing', async () => {
await writeAgentAuthProfiles('main', {
version: 1,
profiles: {
'custom-abc12345:backup': {
type: 'api_key',
provider: 'custom-abc12345',
key: 'sk-backup',
},
},
order: {
'custom-abc12345': [
'custom-abc12345:default',
'custom-abc12345:backup',
],
},
lastGood: {
'custom-abc12345': 'custom-abc12345:default',
},
});
const { removeProviderKeyFromOpenClaw } = await import('@electron/utils/openclaw-auth');
await removeProviderKeyFromOpenClaw('custom-abc12345', 'main');
const mainProfiles = await readAuthProfiles('main');
expect(mainProfiles.profiles).toEqual({
'custom-abc12345:backup': {
type: 'api_key',
provider: 'custom-abc12345',
key: 'sk-backup',
},
});
expect(mainProfiles.order).toEqual({
'custom-abc12345': ['custom-abc12345:backup'],
});
expect(mainProfiles.lastGood).toEqual({});
});
it('does not remove oauth default profiles when deleting only an api key', async () => {
await writeAgentAuthProfiles('main', {
version: 1,
profiles: {
'openai-codex:default': {
type: 'oauth',
provider: 'openai-codex',
access: 'acc',
refresh: 'ref',
expires: 1,
},
},
order: {
'openai-codex': ['openai-codex:default'],
},
lastGood: {
'openai-codex': 'openai-codex:default',
},
});
const { removeProviderKeyFromOpenClaw } = await import('@electron/utils/openclaw-auth');
await removeProviderKeyFromOpenClaw('openai-codex', 'main');
const mainProfiles = await readAuthProfiles('main');
expect(mainProfiles.profiles).toEqual({
'openai-codex:default': {
type: 'oauth',
provider: 'openai-codex',
access: 'acc',
refresh: 'ref',
expires: 1,
},
});
expect(mainProfiles.order).toEqual({
'openai-codex': ['openai-codex:default'],
});
expect(mainProfiles.lastGood).toEqual({
'openai-codex': 'openai-codex:default',
});
});
it('removes api-key defaults for oauth-capable providers that support api keys', async () => {
await writeAgentAuthProfiles('main', {
version: 1,
profiles: {
'minimax-portal:default': {
type: 'api_key',
provider: 'minimax-portal',
key: 'sk-minimax',
},
'minimax-portal:oauth-backup': {
type: 'oauth',
provider: 'minimax-portal',
access: 'acc',
refresh: 'ref',
expires: 1,
},
},
order: {
'minimax-portal': [
'minimax-portal:default',
'minimax-portal:oauth-backup',
],
},
lastGood: {
'minimax-portal': 'minimax-portal:default',
},
});
const { removeProviderKeyFromOpenClaw } = await import('@electron/utils/openclaw-auth');
await removeProviderKeyFromOpenClaw('minimax-portal', 'main');
const mainProfiles = await readAuthProfiles('main');
expect(mainProfiles.profiles).toEqual({
'minimax-portal:oauth-backup': {
type: 'oauth',
provider: 'minimax-portal',
access: 'acc',
refresh: 'ref',
expires: 1,
},
});
expect(mainProfiles.order).toEqual({
'minimax-portal': ['minimax-portal:oauth-backup'],
});
expect(mainProfiles.lastGood).toEqual({});
});
});
describe('sanitizeOpenClawConfig', () => {
beforeEach(async () => {
vi.resetModules();
@@ -292,4 +479,86 @@ describe('auth-backed provider discovery', () => {
anthropic: {},
});
});
it('removes all matching auth profiles for a deleted provider so it does not reappear', async () => {
await writeOpenClawJson({
agents: {
list: [
{ id: 'main', name: 'Main', default: true, workspace: '~/.openclaw/workspace', agentDir: '~/.openclaw/agents/main/agent' },
{ id: 'work', name: 'Work', workspace: '~/.openclaw/workspace-work', agentDir: '~/.openclaw/agents/work/agent' },
],
},
models: {
providers: {
'custom-abc12345': {
baseUrl: 'https://api.moonshot.cn/v1',
api: 'openai-completions',
},
},
},
auth: {
profiles: {
'custom-abc12345:oauth': {
type: 'oauth',
provider: 'custom-abc12345',
access: 'acc',
refresh: 'ref',
expires: 1,
},
'custom-abc12345:secondary': {
type: 'api_key',
provider: 'custom-abc12345',
key: 'sk-inline',
},
},
},
});
await writeAgentAuthProfiles('main', {
version: 1,
profiles: {
'custom-abc12345:default': {
type: 'api_key',
provider: 'custom-abc12345',
key: 'sk-main',
},
'custom-abc12345:backup': {
type: 'api_key',
provider: 'custom-abc12345',
key: 'sk-backup',
},
},
order: {
'custom-abc12345': [
'custom-abc12345:default',
'custom-abc12345:backup',
],
},
lastGood: {
'custom-abc12345': 'custom-abc12345:backup',
},
});
const {
getActiveOpenClawProviders,
getOpenClawProvidersConfig,
removeProviderFromOpenClaw,
} = await import('@electron/utils/openclaw-auth');
await expect(getActiveOpenClawProviders()).resolves.toEqual(new Set(['custom-abc12345']));
await removeProviderFromOpenClaw('custom-abc12345');
const mainProfiles = await readAuthProfiles('main');
const config = await readOpenClawJson();
const result = await getOpenClawProvidersConfig();
expect(mainProfiles.profiles).toEqual({});
expect(mainProfiles.order).toEqual({});
expect(mainProfiles.lastGood).toEqual({});
expect((config.auth as { profiles?: Record<string, unknown> }).profiles).toEqual({});
expect((config.models as { providers?: Record<string, unknown> }).providers).toEqual({});
expect(result.providers).toEqual({});
await expect(getActiveOpenClawProviders()).resolves.toEqual(new Set());
});
});