2025-07-08 09:37:10 -07:00
|
|
|
/**
|
|
|
|
|
* @license
|
|
|
|
|
* Copyright 2025 Google LLC
|
|
|
|
|
* SPDX-License-Identifier: Apache-2.0
|
|
|
|
|
*/
|
|
|
|
|
|
2025-08-17 12:43:21 -04:00
|
|
|
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
|
2025-08-23 05:01:01 +09:00
|
|
|
import {
|
|
|
|
|
setupUser,
|
|
|
|
|
ProjectIdRequiredError,
|
|
|
|
|
ProjectAccessError,
|
|
|
|
|
} from './setup.js';
|
2025-07-08 09:37:10 -07:00
|
|
|
import { CodeAssistServer } from '../code_assist/server.js';
|
|
|
|
|
import { OAuth2Client } from 'google-auth-library';
|
|
|
|
|
import { GeminiUserTier, UserTierId } from './types.js';
|
2025-08-23 05:01:01 +09:00
|
|
|
import { AuthType } from '../core/contentGenerator.js';
|
2025-07-08 09:37:10 -07:00
|
|
|
|
|
|
|
|
vi.mock('../code_assist/server.js');
|
|
|
|
|
|
|
|
|
|
const mockPaidTier: GeminiUserTier = {
|
|
|
|
|
id: UserTierId.STANDARD,
|
|
|
|
|
name: 'paid',
|
|
|
|
|
description: 'Paid tier',
|
2025-08-13 14:04:58 -07:00
|
|
|
isDefault: true,
|
2025-07-08 09:37:10 -07:00
|
|
|
};
|
|
|
|
|
|
2025-08-13 14:04:58 -07:00
|
|
|
const mockFreeTier: GeminiUserTier = {
|
|
|
|
|
id: UserTierId.FREE,
|
|
|
|
|
name: 'free',
|
|
|
|
|
description: 'Free tier',
|
|
|
|
|
isDefault: true,
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
describe('setupUser for existing user', () => {
|
2025-07-08 09:37:10 -07:00
|
|
|
let mockLoad: ReturnType<typeof vi.fn>;
|
|
|
|
|
let mockOnboardUser: ReturnType<typeof vi.fn>;
|
|
|
|
|
|
|
|
|
|
beforeEach(() => {
|
|
|
|
|
vi.resetAllMocks();
|
|
|
|
|
mockLoad = vi.fn();
|
|
|
|
|
mockOnboardUser = vi.fn().mockResolvedValue({
|
|
|
|
|
done: true,
|
|
|
|
|
response: {
|
|
|
|
|
cloudaicompanionProject: {
|
|
|
|
|
id: 'server-project',
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
});
|
|
|
|
|
vi.mocked(CodeAssistServer).mockImplementation(
|
|
|
|
|
() =>
|
|
|
|
|
({
|
|
|
|
|
loadCodeAssist: mockLoad,
|
|
|
|
|
onboardUser: mockOnboardUser,
|
|
|
|
|
}) as unknown as CodeAssistServer,
|
|
|
|
|
);
|
|
|
|
|
});
|
|
|
|
|
|
2025-08-17 12:43:21 -04:00
|
|
|
afterEach(() => {
|
|
|
|
|
vi.unstubAllEnvs();
|
|
|
|
|
});
|
|
|
|
|
|
2025-08-13 14:04:58 -07:00
|
|
|
it('should use GOOGLE_CLOUD_PROJECT when set and project from server is undefined', async () => {
|
2025-08-17 12:43:21 -04:00
|
|
|
vi.stubEnv('GOOGLE_CLOUD_PROJECT', 'test-project');
|
2025-07-08 09:37:10 -07:00
|
|
|
mockLoad.mockResolvedValue({
|
|
|
|
|
currentTier: mockPaidTier,
|
2025-08-23 05:01:01 +09:00
|
|
|
cloudaicompanionProject: 'test-project',
|
2025-07-08 09:37:10 -07:00
|
|
|
});
|
2025-08-23 05:01:01 +09:00
|
|
|
await setupUser({} as OAuth2Client, AuthType.LOGIN_WITH_GOOGLE_GCA);
|
2025-07-08 09:37:10 -07:00
|
|
|
expect(CodeAssistServer).toHaveBeenCalledWith(
|
2025-08-01 14:37:56 -05:00
|
|
|
{},
|
2025-07-08 09:37:10 -07:00
|
|
|
'test-project',
|
2025-08-01 14:37:56 -05:00
|
|
|
{},
|
|
|
|
|
'',
|
|
|
|
|
undefined,
|
2025-07-08 09:37:10 -07:00
|
|
|
);
|
|
|
|
|
});
|
|
|
|
|
|
2025-08-13 14:04:58 -07:00
|
|
|
it('should ignore GOOGLE_CLOUD_PROJECT when project from server is set', async () => {
|
2025-08-17 12:43:21 -04:00
|
|
|
vi.stubEnv('GOOGLE_CLOUD_PROJECT', 'test-project');
|
2025-07-08 09:37:10 -07:00
|
|
|
mockLoad.mockResolvedValue({
|
|
|
|
|
cloudaicompanionProject: 'server-project',
|
|
|
|
|
currentTier: mockPaidTier,
|
|
|
|
|
});
|
2025-08-23 05:01:01 +09:00
|
|
|
const projectId = await setupUser(
|
|
|
|
|
{} as OAuth2Client,
|
|
|
|
|
AuthType.LOGIN_WITH_GOOGLE_GCA,
|
|
|
|
|
);
|
2025-07-08 09:37:10 -07:00
|
|
|
expect(CodeAssistServer).toHaveBeenCalledWith(
|
2025-08-01 14:37:56 -05:00
|
|
|
{},
|
2025-08-13 14:04:58 -07:00
|
|
|
'test-project',
|
2025-08-01 14:37:56 -05:00
|
|
|
{},
|
|
|
|
|
'',
|
2025-07-08 09:37:10 -07:00
|
|
|
undefined,
|
|
|
|
|
);
|
2025-07-21 13:44:43 -07:00
|
|
|
expect(projectId).toEqual({
|
|
|
|
|
projectId: 'server-project',
|
|
|
|
|
userTier: 'standard-tier',
|
|
|
|
|
});
|
2025-07-08 09:37:10 -07:00
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('should throw ProjectIdRequiredError when no project ID is available', async () => {
|
2025-08-17 12:43:21 -04:00
|
|
|
vi.stubEnv('GOOGLE_CLOUD_PROJECT', '');
|
2025-07-08 09:37:10 -07:00
|
|
|
// And the server itself requires a project ID internally
|
|
|
|
|
vi.mocked(CodeAssistServer).mockImplementation(() => {
|
|
|
|
|
throw new ProjectIdRequiredError();
|
|
|
|
|
});
|
|
|
|
|
|
2025-08-23 05:01:01 +09:00
|
|
|
await expect(
|
|
|
|
|
setupUser({} as OAuth2Client, AuthType.LOGIN_WITH_GOOGLE_GCA),
|
|
|
|
|
).rejects.toThrow(ProjectIdRequiredError);
|
2025-07-08 09:37:10 -07:00
|
|
|
});
|
|
|
|
|
});
|
2025-08-13 14:04:58 -07:00
|
|
|
|
|
|
|
|
describe('setupUser for new user', () => {
|
|
|
|
|
let mockLoad: ReturnType<typeof vi.fn>;
|
|
|
|
|
let mockOnboardUser: ReturnType<typeof vi.fn>;
|
|
|
|
|
|
|
|
|
|
beforeEach(() => {
|
|
|
|
|
vi.resetAllMocks();
|
|
|
|
|
mockLoad = vi.fn();
|
|
|
|
|
mockOnboardUser = vi.fn().mockResolvedValue({
|
|
|
|
|
done: true,
|
|
|
|
|
response: {
|
|
|
|
|
cloudaicompanionProject: {
|
|
|
|
|
id: 'server-project',
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
});
|
|
|
|
|
vi.mocked(CodeAssistServer).mockImplementation(
|
|
|
|
|
() =>
|
|
|
|
|
({
|
|
|
|
|
loadCodeAssist: mockLoad,
|
|
|
|
|
onboardUser: mockOnboardUser,
|
|
|
|
|
}) as unknown as CodeAssistServer,
|
|
|
|
|
);
|
|
|
|
|
});
|
|
|
|
|
|
2025-08-17 12:43:21 -04:00
|
|
|
afterEach(() => {
|
|
|
|
|
vi.unstubAllEnvs();
|
|
|
|
|
});
|
|
|
|
|
|
2025-08-13 14:04:58 -07:00
|
|
|
it('should use GOOGLE_CLOUD_PROJECT when set and onboard a new paid user', async () => {
|
2025-08-17 12:43:21 -04:00
|
|
|
vi.stubEnv('GOOGLE_CLOUD_PROJECT', 'test-project');
|
2025-08-13 14:04:58 -07:00
|
|
|
mockLoad.mockResolvedValue({
|
|
|
|
|
allowedTiers: [mockPaidTier],
|
|
|
|
|
});
|
2025-08-23 05:01:01 +09:00
|
|
|
const userData = await setupUser(
|
|
|
|
|
{} as OAuth2Client,
|
|
|
|
|
AuthType.LOGIN_WITH_GOOGLE_GCA,
|
|
|
|
|
);
|
2025-08-13 14:04:58 -07:00
|
|
|
expect(CodeAssistServer).toHaveBeenCalledWith(
|
|
|
|
|
{},
|
|
|
|
|
'test-project',
|
|
|
|
|
{},
|
|
|
|
|
'',
|
|
|
|
|
undefined,
|
|
|
|
|
);
|
|
|
|
|
expect(mockLoad).toHaveBeenCalled();
|
|
|
|
|
expect(mockOnboardUser).toHaveBeenCalledWith({
|
|
|
|
|
tierId: 'standard-tier',
|
|
|
|
|
cloudaicompanionProject: 'test-project',
|
|
|
|
|
metadata: {
|
|
|
|
|
ideType: 'IDE_UNSPECIFIED',
|
|
|
|
|
platform: 'PLATFORM_UNSPECIFIED',
|
|
|
|
|
pluginType: 'GEMINI',
|
|
|
|
|
duetProject: 'test-project',
|
|
|
|
|
},
|
|
|
|
|
});
|
|
|
|
|
expect(userData).toEqual({
|
|
|
|
|
projectId: 'server-project',
|
|
|
|
|
userTier: 'standard-tier',
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('should onboard a new free user when GOOGLE_CLOUD_PROJECT is not set', async () => {
|
2025-08-17 12:43:21 -04:00
|
|
|
vi.stubEnv('GOOGLE_CLOUD_PROJECT', '');
|
2025-08-13 14:04:58 -07:00
|
|
|
mockLoad.mockResolvedValue({
|
|
|
|
|
allowedTiers: [mockFreeTier],
|
|
|
|
|
});
|
2025-08-23 05:01:01 +09:00
|
|
|
const userData = await setupUser(
|
|
|
|
|
{} as OAuth2Client,
|
|
|
|
|
AuthType.LOGIN_WITH_GOOGLE,
|
|
|
|
|
);
|
2025-08-13 14:04:58 -07:00
|
|
|
expect(CodeAssistServer).toHaveBeenCalledWith(
|
|
|
|
|
{},
|
|
|
|
|
undefined,
|
|
|
|
|
{},
|
|
|
|
|
'',
|
|
|
|
|
undefined,
|
|
|
|
|
);
|
|
|
|
|
expect(mockLoad).toHaveBeenCalled();
|
|
|
|
|
expect(mockOnboardUser).toHaveBeenCalledWith({
|
|
|
|
|
tierId: 'free-tier',
|
|
|
|
|
cloudaicompanionProject: undefined,
|
|
|
|
|
metadata: {
|
|
|
|
|
ideType: 'IDE_UNSPECIFIED',
|
|
|
|
|
platform: 'PLATFORM_UNSPECIFIED',
|
|
|
|
|
pluginType: 'GEMINI',
|
|
|
|
|
},
|
|
|
|
|
});
|
|
|
|
|
expect(userData).toEqual({
|
|
|
|
|
projectId: 'server-project',
|
|
|
|
|
userTier: 'free-tier',
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
|
2025-08-23 05:01:01 +09:00
|
|
|
it('should throw ProjectAccessError when LOGIN_WITH_GOOGLE_GCA onboard response has no project ID', async () => {
|
2025-08-17 12:43:21 -04:00
|
|
|
vi.stubEnv('GOOGLE_CLOUD_PROJECT', 'test-project');
|
2025-08-13 14:04:58 -07:00
|
|
|
mockLoad.mockResolvedValue({
|
|
|
|
|
allowedTiers: [mockPaidTier],
|
|
|
|
|
});
|
|
|
|
|
mockOnboardUser.mockResolvedValue({
|
|
|
|
|
done: true,
|
|
|
|
|
response: {
|
|
|
|
|
cloudaicompanionProject: undefined,
|
|
|
|
|
},
|
|
|
|
|
});
|
2025-08-23 05:01:01 +09:00
|
|
|
await expect(
|
|
|
|
|
setupUser({} as OAuth2Client, AuthType.LOGIN_WITH_GOOGLE_GCA),
|
|
|
|
|
).rejects.toThrow(ProjectAccessError);
|
2025-08-13 14:04:58 -07:00
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('should throw ProjectIdRequiredError when no project ID is available', async () => {
|
2025-08-17 12:43:21 -04:00
|
|
|
vi.stubEnv('GOOGLE_CLOUD_PROJECT', '');
|
2025-08-13 14:04:58 -07:00
|
|
|
mockLoad.mockResolvedValue({
|
|
|
|
|
allowedTiers: [mockPaidTier],
|
|
|
|
|
});
|
|
|
|
|
mockOnboardUser.mockResolvedValue({
|
|
|
|
|
done: true,
|
|
|
|
|
response: {},
|
|
|
|
|
});
|
2025-08-23 05:01:01 +09:00
|
|
|
await expect(
|
|
|
|
|
setupUser({} as OAuth2Client, AuthType.LOGIN_WITH_GOOGLE_GCA),
|
|
|
|
|
).rejects.toThrow(ProjectIdRequiredError);
|
2025-08-13 14:04:58 -07:00
|
|
|
});
|
|
|
|
|
});
|