Skip to content

Commit c18f23e

Browse files
author
Akos Kitta
committed
fix: can unset network#proxy in the CLI config
An empty object (`{}`) must be used to correctly unset the CLI config value to its default. Closes #2184 Signed-off-by: Akos Kitta <[email protected]>
1 parent 405b4fe commit c18f23e

File tree

2 files changed

+179
-1
lines changed

2 files changed

+179
-1
lines changed

arduino-ide-extension/src/node/config-service-impl.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -95,7 +95,7 @@ export class ConfigServiceImpl
9595
};
9696
copyDefaultCliConfig.locale = locale || 'en';
9797
const proxy = Network.stringify(network);
98-
copyDefaultCliConfig.network = { proxy };
98+
copyDefaultCliConfig.network = proxy ? { proxy } : {}; // must be an empty object to unset the default prop with the `WriteRequest`.
9999

100100
// always use the port of the daemon
101101
const port = await this.daemon.getPort();
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,178 @@
1+
import {
2+
Disposable,
3+
DisposableCollection,
4+
} from '@theia/core/lib/common/disposable';
5+
import { deepClone } from '@theia/core/lib/common/objects';
6+
import type { MaybePromise, Mutable } from '@theia/core/lib/common/types';
7+
import type { Container } from '@theia/core/shared/inversify';
8+
import { expect } from 'chai';
9+
import { load as parseYaml } from 'js-yaml';
10+
import { promises as fs } from 'node:fs';
11+
import { join } from 'node:path';
12+
import temp from 'temp';
13+
import {
14+
Config,
15+
Network,
16+
ProxySettings,
17+
} from '../../common/protocol/config-service';
18+
import { CLI_CONFIG, DefaultCliConfig } from '../../node/cli-config';
19+
import { ConfigServiceImpl } from '../../node/config-service-impl';
20+
import { ConfigDirUriProvider } from '../../node/theia/env-variables/env-variables-server';
21+
import {
22+
createBaseContainer,
23+
createCliConfig,
24+
startDaemon,
25+
} from './node-test-bindings';
26+
27+
describe('config-service-impl', () => {
28+
const noProxy = 'none';
29+
const manualProxy: ProxySettings = {
30+
protocol: 'http',
31+
hostname: 'hostname',
32+
password: 'secret',
33+
username: 'username',
34+
port: '1234',
35+
};
36+
37+
describe('setConfiguration', () => {
38+
let configService: ConfigServiceImpl;
39+
let toDispose: DisposableCollection;
40+
let cliConfigPath: string;
41+
42+
beforeEach(async () => {
43+
const container = await createBaseContainer();
44+
toDispose = new DisposableCollection();
45+
await startDaemon(container, toDispose);
46+
configService = container.get<ConfigServiceImpl>(ConfigServiceImpl);
47+
cliConfigPath = getCliConfigPath(container);
48+
});
49+
50+
afterEach(() => toDispose.dispose());
51+
52+
it("should detect 'none' proxy with th default config", async () => {
53+
const state = await configService.getConfiguration();
54+
expect(state.config).to.be.not.undefined;
55+
const config = <Config>state.config;
56+
expect(config.network).to.be.equal(noProxy);
57+
expect(Network.stringify(config.network)).is.undefined;
58+
await assertRawConfigModel(cliConfigPath, (actualModel) => {
59+
expect(actualModel.network).to.be.undefined;
60+
});
61+
});
62+
63+
it('should ignore noop changes', async () => {
64+
const beforeState = await configService.getConfiguration();
65+
const config = <Mutable<Config>>deepClone(beforeState).config;
66+
let eventCounter = 0;
67+
toDispose.push(configService.onConfigChange(() => eventCounter++));
68+
await configService.setConfiguration(config);
69+
const afterState = await configService.getConfiguration();
70+
expect(beforeState.config).to.be.deep.equal(afterState.config);
71+
expect(eventCounter).to.be.equal(0);
72+
});
73+
74+
it('should set the manual proxy', async () => {
75+
const beforeState = await configService.getConfiguration();
76+
const config = <Mutable<Config>>deepClone(beforeState).config;
77+
config.network = manualProxy;
78+
let eventCounter = 0;
79+
toDispose.push(configService.onConfigChange(() => eventCounter++));
80+
await configService.setConfiguration(config);
81+
const afterState = await configService.getConfiguration();
82+
expect(beforeState.config).to.be.not.deep.equal(afterState.config);
83+
expect(afterState.config?.network).to.be.deep.equal(manualProxy);
84+
expect(eventCounter).to.be.equal(1);
85+
await assertRawConfigModel(cliConfigPath, (actualModel) => {
86+
expect(actualModel.network?.proxy).to.be.equal(
87+
Network.stringify(manualProxy)
88+
);
89+
});
90+
});
91+
92+
it('should unset the manual proxy', async () => {
93+
const initialState = await configService.getConfiguration();
94+
const config = <Mutable<Config>>deepClone(initialState).config;
95+
config.network = manualProxy;
96+
let eventCounter = 0;
97+
toDispose.push(configService.onConfigChange(() => eventCounter++));
98+
await configService.setConfiguration(config);
99+
const beforeState = await configService.getConfiguration();
100+
const config2 = <Mutable<Config>>deepClone(config);
101+
config2.network = noProxy;
102+
await configService.setConfiguration(config2);
103+
const afterState = await configService.getConfiguration();
104+
expect(beforeState.config).to.be.not.deep.equal(afterState.config);
105+
expect(afterState.config?.network).to.be.deep.equal(noProxy);
106+
expect(eventCounter).to.be.equal(2);
107+
await assertRawConfigModel(cliConfigPath, (actualModel) => {
108+
expect(actualModel.network?.proxy).to.be.undefined;
109+
});
110+
});
111+
});
112+
113+
describe('setConfiguration (multiple CLI daemon sessions)', () => {
114+
let tracked: typeof temp;
115+
let toDispose: DisposableCollection;
116+
117+
before(() => {
118+
tracked = temp.track();
119+
toDispose = new DisposableCollection(
120+
Disposable.create(() => tracked.cleanupSync())
121+
);
122+
});
123+
124+
after(() => toDispose.dispose());
125+
126+
it("should unset the 'network#proxy' config value between daemon sessions", async () => {
127+
const configDirPath = tracked.mkdirSync();
128+
const cliConfigPath = join(configDirPath, CLI_CONFIG);
129+
const cliConfig = await createCliConfig(configDirPath);
130+
const setupContainer = await createBaseContainer({
131+
cliConfig,
132+
configDirPath,
133+
});
134+
const toDisposeAfterFirstStart = new DisposableCollection();
135+
toDispose.push(toDisposeAfterFirstStart);
136+
await startDaemon(setupContainer, toDisposeAfterFirstStart);
137+
toDisposeAfterFirstStart.dispose();
138+
139+
// second startup when the indexes are all downloaded and the daemon is initialized with the network#proxy
140+
cliConfig.network = { proxy: Network.stringify(manualProxy) };
141+
const container = await createBaseContainer({ cliConfig, configDirPath });
142+
await startDaemon(container, toDispose);
143+
const configService = container.get<ConfigServiceImpl>(ConfigServiceImpl);
144+
let eventCounter = 0;
145+
toDispose.push(configService.onConfigChange(() => eventCounter++));
146+
147+
const beforeState = await configService.getConfiguration();
148+
const config = <Mutable<Config>>deepClone(beforeState.config);
149+
config.network = noProxy;
150+
await configService.setConfiguration(config);
151+
const afterState = await configService.getConfiguration();
152+
expect(beforeState.config).to.be.not.deep.equal(afterState.config);
153+
expect(afterState.config?.network).to.be.deep.equal(noProxy);
154+
expect(eventCounter).to.be.equal(1);
155+
await assertRawConfigModel(cliConfigPath, (actualModel) => {
156+
expect(actualModel.network?.proxy).to.be.undefined; // currently fails due to arduino/arduino-cli#2275
157+
});
158+
});
159+
});
160+
161+
async function assertRawConfigModel(
162+
cliConfigPath: string,
163+
assert: (actual: DefaultCliConfig) => MaybePromise<void>
164+
): Promise<void> {
165+
const raw = await fs.readFile(cliConfigPath, { encoding: 'utf8' });
166+
const model = parseYaml(raw);
167+
await assert(model);
168+
}
169+
170+
function getCliConfigPath(container: Container): string {
171+
const configDirUriProvider =
172+
container.get<ConfigDirUriProvider>(ConfigDirUriProvider);
173+
return configDirUriProvider
174+
.configDirUri()
175+
.resolve(CLI_CONFIG)
176+
.path.fsPath();
177+
}
178+
});

0 commit comments

Comments
 (0)