Skip to content

Commit 1eb5fb3

Browse files
committed
Agent Host changes for agents/ensure-managed-settings-stability
1 parent cc9dea5 commit 1eb5fb3

1 file changed

Lines changed: 163 additions & 1 deletion

File tree

src/vs/workbench/services/accounts/test/browser/defaultAccount.test.ts

Lines changed: 163 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,8 +28,9 @@ import { AuthenticationSession, AuthenticationSessionsChangeEvent, IAuthenticati
2828
import { IWorkbenchEnvironmentService } from '../../../environment/common/environmentService.js';
2929
import { IExtensionService } from '../../../extensions/common/extensions.js';
3030
import { IHostService } from '../../../host/browser/host.js';
31-
import { DefaultAccountProvider } from '../../browser/defaultAccount.js';
31+
import { DefaultAccountProvider, DefaultAccountService } from '../../browser/defaultAccount.js';
3232
import { TestProductService } from '../../../../test/common/workbenchTestServices.js';
33+
import { AccountPolicyGateState, AccountPolicyService } from '../../../policies/common/accountPolicyService.js';
3334

3435
suite('DefaultAccountProvider', () => {
3536

@@ -575,6 +576,167 @@ suite('DefaultAccountProvider', () => {
575576
assert.strictEqual(requestService.requestCount, 2);
576577
});
577578

579+
test('managed settings endpoint outage without a refresh requirement keeps freshness non-blocking', async () => {
580+
let managedSettingsRequestCount = 0;
581+
const requestService = new TestRequestService(async options => {
582+
if (options.url?.endsWith('/copilot_internal/user')) {
583+
return jsonResponse({ chat_enabled: true });
584+
}
585+
if (options.url?.includes('/copilot_internal/managed_settings')) {
586+
managedSettingsRequestCount++;
587+
if (managedSettingsRequestCount === 1) {
588+
return jsonResponse({
589+
permissions: { disableBypassPermissionsMode: 'disable' },
590+
});
591+
}
592+
throw new Error('managed settings request timed out');
593+
}
594+
throw new Error(`Unexpected request: ${options.url}`);
595+
});
596+
const provider = await createProvider(
597+
requestService,
598+
{},
599+
{},
600+
'https://api.github.com/copilot_internal/managed_settings',
601+
{ getSessions: async () => sessions }
602+
);
603+
604+
await provider.refresh({ forceRefresh: true });
605+
const accountService = disposables.add(new DefaultAccountService(TestProductService));
606+
accountService.setDefaultAccountProvider(provider);
607+
await accountService.refresh();
608+
const gateService = disposables.add(new AccountPolicyService(new NullLogService(), accountService));
609+
610+
assert.deepStrictEqual({
611+
managedSettingsRequestCount,
612+
requestTimeouts: requestService.requests
613+
.filter(request => request.url?.includes('/copilot_internal/managed_settings'))
614+
.map(request => request.timeout),
615+
defaultAccount: provider.defaultAccount,
616+
managedSettings: provider.policyData?.managedSettings,
617+
freshness: provider.managedSettingsFreshness,
618+
compatibilityError: provider.managedSettingsCompatibilityError,
619+
gateState: gateService.gateInfo.state,
620+
}, {
621+
managedSettingsRequestCount: 2,
622+
requestTimeouts: [5000, 5000],
623+
defaultAccount: {
624+
authenticationProvider: { id: 'github', name: 'GitHub', enterprise: false },
625+
accountName: 'octocat',
626+
sessionId: 'session',
627+
enterprise: false,
628+
entitlementsData: { chat_enabled: true },
629+
},
630+
managedSettings: {
631+
'permissions.disableBypassPermissionsMode': 'disable',
632+
},
633+
freshness: { state: ManagedSettingsFreshnessState.NotRequired },
634+
compatibilityError: null,
635+
gateState: AccountPolicyGateState.Inactive,
636+
});
637+
});
638+
639+
test('truthy non-boolean server refresh control remains non-blocking after an endpoint outage', async () => {
640+
let managedSettingsRequestCount = 0;
641+
const requestService = new TestRequestService(async options => {
642+
if (options.url?.endsWith('/copilot_internal/user')) {
643+
return jsonResponse({ chat_enabled: true });
644+
}
645+
if (options.url?.includes('/copilot_internal/managed_settings')) {
646+
managedSettingsRequestCount++;
647+
if (managedSettingsRequestCount === 1) {
648+
return jsonResponse({
649+
forceRemoteSettingsRefresh: 'true',
650+
permissions: { disableBypassPermissionsMode: 'disable' },
651+
});
652+
}
653+
throw new Error('managed settings request timed out');
654+
}
655+
throw new Error(`Unexpected request: ${options.url}`);
656+
});
657+
const provider = await createProvider(
658+
requestService,
659+
{},
660+
{},
661+
'https://api.github.com/copilot_internal/managed_settings',
662+
{ getSessions: async () => sessions }
663+
);
664+
665+
await provider.refresh({ forceRefresh: true });
666+
667+
assert.deepStrictEqual({
668+
managedSettingsRequestCount,
669+
managedSettings: provider.policyData?.managedSettings,
670+
freshness: provider.managedSettingsFreshness,
671+
compatibilityError: provider.managedSettingsCompatibilityError,
672+
}, {
673+
managedSettingsRequestCount: 2,
674+
managedSettings: {
675+
forceRemoteSettingsRefresh: 'true',
676+
'permissions.disableBypassPermissionsMode': 'disable',
677+
},
678+
freshness: { state: ManagedSettingsFreshnessState.NotRequired },
679+
compatibilityError: null,
680+
});
681+
});
682+
683+
test('non-466 managed settings failures remain non-blocking without a refresh requirement', async () => {
684+
const failures: { name: string; response: () => IRequestContext }[] = [
685+
{ name: 'http error', response: () => jsonResponse({}, 500) },
686+
{
687+
name: 'malformed response',
688+
response: () => ({
689+
res: { statusCode: 200, headers: {} },
690+
stream: bufferToStream(VSBuffer.fromString('{')),
691+
}),
692+
},
693+
{ name: 'rate limited', response: () => jsonResponse({}, 429, { 'retry-after': '60' }) },
694+
];
695+
const outcomes = [];
696+
697+
for (const failure of failures) {
698+
const requestService = new TestRequestService(async () => failure.response());
699+
const provider = await createProvider(requestService);
700+
const cachedPolicy = createCachedPolicy(false);
701+
const result = await provider['getManagedSettings'](sessions, cachedPolicy, { forceRefresh: true });
702+
outcomes.push({
703+
name: failure.name,
704+
status: provider.managedSettingsFetchStatus,
705+
data: result.data,
706+
keptCachedTimestamp: result.fetchedAt === cachedPolicy.managedSettingsFetchedAt,
707+
freshness: provider.managedSettingsFreshness,
708+
compatibilityError: provider.managedSettingsCompatibilityError,
709+
});
710+
}
711+
712+
assert.deepStrictEqual(outcomes, [
713+
{
714+
name: 'http error',
715+
status: 500,
716+
data: createCachedPolicy(false).policyData,
717+
keptCachedTimestamp: true,
718+
freshness: { state: ManagedSettingsFreshnessState.NotRequired },
719+
compatibilityError: null,
720+
},
721+
{
722+
name: 'malformed response',
723+
status: 'parse-error',
724+
data: createCachedPolicy(false).policyData,
725+
keptCachedTimestamp: true,
726+
freshness: { state: ManagedSettingsFreshnessState.NotRequired },
727+
compatibilityError: null,
728+
},
729+
{
730+
name: 'rate limited',
731+
status: 429,
732+
data: createCachedPolicy(false).policyData,
733+
keptCachedTimestamp: true,
734+
freshness: { state: ManagedSettingsFreshnessState.NotRequired },
735+
compatibilityError: null,
736+
},
737+
]);
738+
});
739+
578740
test('managed settings source change preserves a prior blocked state', async () => {
579741
const requestService = new TestRequestService(async () => {
580742
throw new Error('managed settings unavailable');

0 commit comments

Comments
 (0)