Revert "add authentication (login/signup) and improve search history management" (#1120)
Some checks are pending
CodeQL Advanced / Analyze (actions) (push) Waiting to run
CodeQL Advanced / Analyze (javascript-typescript) (push) Waiting to run
CodeQL Advanced / Analyze (python) (push) Waiting to run
Pre-commit / pre-commit (push) Waiting to run

This commit is contained in:
Wendong-Fan 2026-02-01 17:23:35 +00:00 committed by GitHub
parent bd2f2a1c9e
commit 3291fdf0eb
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
14 changed files with 223 additions and 462 deletions

2
.gitignore vendored
View file

@ -19,7 +19,7 @@ release
backend/context_files/
# Editor directories and files
.vscode/
.vscode/.debug.env
.idea
.DS_Store
*.suo

23
.vscode/.debug.script.mjs vendored Normal file
View file

@ -0,0 +1,23 @@
import fs from 'node:fs'
import path from 'node:path'
import { fileURLToPath } from 'node:url'
import { createRequire } from 'node:module'
import { spawn } from 'node:child_process'
const pkg = createRequire(import.meta.url)('../package.json')
const __dirname = path.dirname(fileURLToPath(import.meta.url))
// write .debug.env
const envContent = Object.entries(pkg.debug.env).map(([key, val]) => `${key}=${val}`)
fs.writeFileSync(path.join(__dirname, '.debug.env'), envContent.join('\n'))
// bootstrap
spawn(
// TODO: terminate `npm run dev` when Debug exits.
process.platform === 'win32' ? 'npm.cmd' : 'npm',
['run', 'dev'],
{
stdio: 'inherit',
env: Object.assign(process.env, { VSCODE_DEBUG: 'true' }),
},
)

13
.vscode/extensions.json vendored Normal file
View file

@ -0,0 +1,13 @@
{
// See http://go.microsoft.com/fwlink/?LinkId=827846
// for the documentation about the extensions.json format
"recommendations": [
"mrmlnc.vscode-json5",
"ms-python.python",
"ms-python.debugpy",
// Linting / Formatting
"esbenp.prettier-vscode",
"dbaeumer.vscode-eslint",
"bradlc.vscode-tailwindcss"
]
}

70
.vscode/launch.json vendored Normal file
View file

@ -0,0 +1,70 @@
{
// Use IntelliSense to learn about possible attributes.
// Hover to view descriptions of existing attributes.
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"compounds": [
{
"name": "Debug App",
"preLaunchTask": "Before Debug",
"configurations": [
"Debug Main Process",
"Debug Renderer Process"
],
"presentation": {
"hidden": false,
"group": "",
"order": 1
},
"stopAll": true
}
],
"configurations": [
{
"name": "Debug Main Process",
"type": "node",
"request": "launch",
"runtimeExecutable": "${workspaceRoot}/node_modules/.bin/electron",
"windows": {
"runtimeExecutable": "${workspaceRoot}/node_modules/.bin/electron.cmd"
},
"runtimeArgs": [
"--no-sandbox",
"--remote-debugging-port=9229",
"."
],
"envFile": "${workspaceFolder}/.vscode/.debug.env",
"console": "integratedTerminal"
},
{
"name": "Debug Renderer Process",
"port": 9229,
"request": "attach",
"type": "chrome",
"timeout": 60000,
"skipFiles": [
"<node_internals>/**",
"${workspaceRoot}/node_modules/**",
"${workspaceRoot}/dist-electron/**",
// Skip files in host(VITE_DEV_SERVER_URL)
"http://127.0.0.1:7777/**"
]
},
{
"name": "Debug Python Backend (Attach)",
"type": "debugpy",
"request": "attach",
"connect": {
"host": "localhost",
"port": 5678
},
"pathMappings": [
{
"localRoot": "${workspaceFolder}/backend",
"remoteRoot": "."
}
],
"justMyCode": false
}
]
}

54
.vscode/settings.json vendored Normal file
View file

@ -0,0 +1,54 @@
{
"typescript.tsdk": "node_modules/typescript/lib",
"typescript.tsc.autoDetect": "off",
"json.schemas": [
{
"fileMatch": [
"/*electron-builder.json5",
"/*electron-builder.json"
],
"url": "https://json.schemastore.org/electron-builder"
}
],
"cSpell.words": [
"Eigent"
],
"i18n-ally.localesPaths": [
"backend/lang",
"server/lang",
"src/i18n",
"src/i18n/locales"
],
// Prettier & ESLint configuration
"editor.defaultFormatter": "esbenp.prettier-vscode",
"editor.formatOnSave": true,
"editor.codeActionsOnSave": {
"source.fixAll.eslint": "explicit",
"source.organizeImports": "explicit"
},
"[javascript]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
},
"[javascriptreact]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
},
"[typescript]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
},
"[typescriptreact]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
},
"[json]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
},
"[jsonc]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
},
"prettier.requireConfig": true,
"eslint.validate": [
"javascript",
"javascriptreact",
"typescript",
"typescriptreact"
]
}

2
.vscode/tasks.json vendored
View file

@ -6,7 +6,7 @@
{
"label": "Before Debug",
"type": "shell",
"command": "node .vscode/.debug.script.js",
"command": "node .vscode/.debug.script.mjs",
"isBackground": true,
"problemMatcher": {
"owner": "typescript",

View file

@ -14,10 +14,9 @@
'use client';
import { ScanFace, Search, Trash2 } from 'lucide-react';
import { ScanFace, Search } from 'lucide-react';
import { useEffect, useState } from 'react';
import { proxyFetchDelete } from '@/api/http';
import GroupedHistoryView from '@/components/GroupedHistoryView';
import {
CommandDialog,
@ -31,7 +30,6 @@ import {
import useChatStoreAdapter from '@/hooks/useChatStoreAdapter';
import { replayProject } from '@/lib';
import { fetchHistoryTasks } from '@/service/historyApi';
import { getAuthStore } from '@/store/authStore';
import { useGlobalStore } from '@/store/globalStore';
import { VisuallyHidden } from '@radix-ui/react-visually-hidden';
import { useTranslation } from 'react-i18next';
@ -76,35 +74,9 @@ export function SearchHistoryDialog() {
await replayProject(projectStore, navigate, projectId, question, historyId);
};
const handleDelete = async (historyId: string, callback?: () => void) => {
try {
await proxyFetchDelete(`/api/chat/history/${historyId}`);
// Also delete local files for this task if available (via Electron IPC)
const history = historyTasks.find(
(item) => String(item.id) === String(historyId)
);
const { email } = getAuthStore();
if (history?.task_id && (window as any).ipcRenderer) {
try {
await (window as any).ipcRenderer.invoke(
'delete-task-files',
email,
history.task_id,
history.project_id ?? undefined
);
} catch (error) {
console.warn('Local file cleanup failed:', error);
}
}
setHistoryTasks((list) =>
list.filter((item) => String(item.id) !== String(historyId))
);
callback?.();
} catch (error) {
console.error('Failed to delete history task:', error);
}
const handleDelete = (taskId: string) => {
// TODO: Implement delete functionality similar to HistorySidebar
console.log('Delete task:', taskId);
};
const handleShare = (taskId: string) => {
@ -165,20 +137,6 @@ export function SearchHistoryDialog() {
<div className="overflow-hidden text-ellipsis whitespace-nowrap">
{task.question}
</div>
<Button
type="button"
variant="ghost"
size="icon"
className="text-muted-foreground hover:text-foreground ml-auto"
aria-label="Delete history"
onClick={(e) => {
e.preventDefault();
e.stopPropagation();
void handleDelete(String(task.id));
}}
>
<Trash2 size={16} />
</Button>
</CommandItem>
))}
</CommandGroup>

View file

@ -28,7 +28,6 @@ import github2 from '@/assets/github2.svg';
import google from '@/assets/google.svg';
import WindowControls from '@/components/WindowControls';
import { hasStackKeys } from '@/lib';
import { loginByStackWithAutoCreate } from '@/service/stackAuthApi';
import { useTranslation } from 'react-i18next';
const HAS_STACK_KEYS = hasStackKeys();
@ -159,20 +158,7 @@ export default function Login() {
return;
}
const token = (data as any)?.token as string | undefined;
const email =
((data as any)?.email as string | undefined) ?? formData.email;
if (!token) {
setGeneralError(t('layout.login-failed-please-try-again'));
return;
}
setAuth({
token,
email,
username: (data as any)?.username ?? null,
user_id: (data as any)?.user_id ?? null,
});
setAuth({ email: formData.email, ...data });
setModelType('cloud');
// Record VITE_USE_LOCAL_PROXY value at login
const localProxyValue = import.meta.env.VITE_USE_LOCAL_PROXY || null;
@ -191,14 +177,12 @@ export default function Login() {
const handleLoginByStack = useCallback(
async (token: string) => {
try {
// 1) Try normal login (existing profile)
// 2) If not found, auto-create profile via signup and continue
const data = await loginByStackWithAutoCreate(token);
if (!data) {
setGeneralError(t('layout.login-failed-please-try-again'));
return;
}
const data = await proxyFetchPost(
'/api/login-by_stack?token=' + token,
{
token: token,
}
);
const errorMessage = getLoginErrorMessage(data);
if (errorMessage) {
@ -207,21 +191,7 @@ export default function Login() {
}
console.log('data', data);
setModelType('cloud');
const authToken = (data as any)?.token as string | undefined;
const email =
((data as any)?.email as string | undefined) ?? formData.email;
if (!authToken) {
setGeneralError(t('layout.login-failed-please-try-again'));
return;
}
setAuth({
token: authToken,
email,
username: (data as any)?.username ?? null,
user_id: (data as any)?.user_id ?? null,
});
setAuth({ email: formData.email, ...data });
// Record VITE_USE_LOCAL_PROXY value at login
const localProxyValue = import.meta.env.VITE_USE_LOCAL_PROXY || null;
setLocalProxyValue(localProxyValue);
@ -250,11 +220,6 @@ export default function Login() {
const handleReloadBtn = async (type: string) => {
if (!app) {
// Keep the buttons visible so users discover the option, but make it
// clear when Stack OAuth isn't configured for local builds.
setGeneralError(
'Social sign-in is not configured for this build. Set VITE_STACK_PROJECT_ID, VITE_STACK_PUBLISHABLE_CLIENT_KEY, and VITE_STACK_SECRET_SERVER_KEY.'
);
console.error('Stack app not initialized');
return;
}
@ -322,12 +287,7 @@ export default function Login() {
lock = true;
setIsLoading(true);
const accessToken = await handleGetToken(code);
if (!accessToken) {
setGeneralError(t('layout.login-failed-please-try-again'));
setIsLoading(false);
return;
}
let accessToken = await handleGetToken(code);
handleLoginByStack(accessToken);
setTimeout(() => {
lock = false;
@ -442,35 +402,39 @@ export default function Login() {
{t('layout.sign-up')}
</Button>
</div>
<div className="w-full pt-6">
<Button
variant="primary"
size="lg"
onClick={() => handleReloadBtn('google')}
className="mb-4 w-full justify-center rounded-[24px] text-center font-inter text-[15px] font-bold leading-[22px] text-[#F5F5F5] transition-all duration-300 ease-in-out"
disabled={isLoading || !HAS_STACK_KEYS}
>
<img src={google} className="h-5 w-5" />
<span className="ml-2">
{t('layout.continue-with-google-login')}
</span>
</Button>
<Button
variant="primary"
size="lg"
onClick={() => handleReloadBtn('github')}
className="mb-4 w-full justify-center rounded-[24px] text-center font-inter text-[15px] font-bold leading-[22px] text-[#F5F5F5] transition-all duration-300 ease-in-out"
disabled={isLoading || !HAS_STACK_KEYS}
>
<img src={github2} className="h-5 w-5" />
<span className="ml-2">
{t('layout.continue-with-github-login')}
</span>
</Button>
</div>
<div className="mb-6 mt-2 w-full text-center font-inter text-[15px] font-medium leading-[22px] text-[#222]">
{t('layout.or')}
</div>
{HAS_STACK_KEYS && (
<div className="w-full pt-6">
<Button
variant="primary"
size="lg"
onClick={() => handleReloadBtn('google')}
className="mb-4 w-full justify-center rounded-[24px] text-center font-inter text-[15px] font-bold leading-[22px] text-[#F5F5F5] transition-all duration-300 ease-in-out"
disabled={isLoading}
>
<img src={google} className="h-5 w-5" />
<span className="ml-2">
{t('layout.continue-with-google-login')}
</span>
</Button>
<Button
variant="primary"
size="lg"
onClick={() => handleReloadBtn('github')}
className="mb-4 w-full justify-center rounded-[24px] text-center font-inter text-[15px] font-bold leading-[22px] text-[#F5F5F5] transition-all duration-300 ease-in-out"
disabled={isLoading}
>
<img src={github2} className="h-5 w-5" />
<span className="ml-2">
{t('layout.continue-with-github-login')}
</span>
</Button>
</div>
)}
{HAS_STACK_KEYS && (
<div className="mb-6 mt-2 w-full text-center font-inter text-[15px] font-medium leading-[22px] text-[#222]">
{t('layout.or')}
</div>
)}
<div className="flex w-full flex-col gap-4">
{generalError && (
<p className="mb-4 mt-1 text-label-md text-text-cuation">

View file

@ -27,7 +27,6 @@ import eye from '@/assets/eye.svg';
import github2 from '@/assets/github2.svg';
import google from '@/assets/google.svg';
import { hasStackKeys } from '@/lib';
import { loginByStackToken } from '@/service/stackAuthApi';
import { useTranslation } from 'react-i18next';
const HAS_STACK_KEYS = hasStackKeys();
@ -151,16 +150,13 @@ export default function SignUp() {
const handleLoginByStack = useCallback(
async (token: string) => {
try {
if (!token) {
setGeneralError(t('layout.login-failed-please-try-again'));
return;
}
const inviteCode = localStorage.getItem('invite_code') || '';
const data = await loginByStackToken({
token,
type: 'signup',
inviteCode: inviteCode || undefined,
});
const data = await proxyFetchPost(
'/api/login-by_stack?token=' + token,
{
token: token,
invite_code: localStorage.getItem('invite_code') || '',
}
);
if (data.code === 10) {
setGeneralError(
@ -169,21 +165,7 @@ export default function SignUp() {
return;
}
console.log('data', data);
const authToken = (data as any)?.token as string | undefined;
const email =
((data as any)?.email as string | undefined) ?? formData.email;
if (!authToken) {
setGeneralError(t('layout.login-failed-please-try-again'));
return;
}
setAuth({
token: authToken,
email,
username: (data as any)?.username ?? null,
user_id: (data as any)?.user_id ?? null,
});
setAuth({ email: formData.email, ...data });
navigate('/');
} catch (error: any) {
console.error('Login failed:', error);
@ -264,17 +246,12 @@ export default function SignUp() {
lock = true;
setIsLoading(true);
const accessToken = await handleGetToken(code);
if (!accessToken) {
setGeneralError(t('layout.login-failed-please-try-again'));
setIsLoading(false);
return;
}
await handleLoginByStack(accessToken);
setTimeout(() => {
lock = false;
}, 1500);
},
[location.pathname, handleLoginByStack, handleGetToken, setIsLoading, t]
[location.pathname, handleLoginByStack, handleGetToken, setIsLoading]
);
useEffect(() => {

View file

@ -1,63 +0,0 @@
// ========= Copyright 2025-2026 @ Eigent.ai All Rights Reserved. =========
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// ========= Copyright 2025-2026 @ Eigent.ai All Rights Reserved. =========
import { proxyFetchPost } from '@/api/http';
export type StackAuthFlowType = 'login' | 'signup';
type StackLoginResponse = {
code?: number;
text?: string;
[key: string]: any;
};
function isUserNotFoundResponse(
res: StackLoginResponse | null | undefined
): boolean {
if (!res || typeof res !== 'object') return false;
if (res.code !== 1) return false;
const text = String(res.text ?? '').toLowerCase();
return text.includes('user not found');
}
export async function loginByStackToken(params: {
token: string;
type: StackAuthFlowType;
inviteCode?: string;
}): Promise<StackLoginResponse> {
const searchParams = new URLSearchParams();
searchParams.set('token', params.token);
searchParams.set('type', params.type);
if (params.inviteCode) {
searchParams.set('invite_code', params.inviteCode);
}
// Endpoint is defined as POST, but consumes query params.
return proxyFetchPost(`/api/login-by_stack?${searchParams.toString()}`, {
token: params.token,
invite_code: params.inviteCode ?? '',
});
}
/**
* Attempts a passwordless SSO login first, and auto-creates the user if not found.
* This matches the UX request: check existing profile; if missing, create like signup.
*/
export async function loginByStackWithAutoCreate(
token: string
): Promise<StackLoginResponse> {
const loginRes = await loginByStackToken({ token, type: 'login' });
if (!isUserNotFoundResponse(loginRes)) return loginRes;
return loginByStackToken({ token, type: 'signup' });
}

View file

@ -32,9 +32,9 @@ type CloudModelType =
// auth info interface
interface AuthInfo {
token: string;
username?: string | null;
username: string;
email: string;
user_id?: number | null;
user_id: number;
}
// auth state interface
@ -107,12 +107,7 @@ const authStore = create<AuthState>()(
// auth related methods
setAuth: ({ token, username, email, user_id }) =>
set({
token,
email,
username: username ?? null,
user_id: user_id ?? null,
}),
set({ token, username, email, user_id }),
logout: () =>
set({

View file

@ -1,148 +0,0 @@
// ========= Copyright 2025-2026 @ Eigent.ai All Rights Reserved. =========
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// ========= Copyright 2025-2026 @ Eigent.ai All Rights Reserved. =========
import { render, screen, waitFor } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import { beforeEach, describe, expect, it, vi } from 'vitest';
import { SearchHistoryDialog } from '../../../src/components/SearchHistoryDialog';
// ---- Mocks ----
vi.mock('react-router-dom', () => ({
useNavigate: () => vi.fn(),
}));
vi.mock('@/hooks/useChatStoreAdapter', () => ({
default: () => ({
chatStore: { activeTaskId: undefined },
projectStore: {
getProjectById: vi.fn(() => null),
setHistoryId: vi.fn(),
setActiveProject: vi.fn(),
},
}),
}));
vi.mock('@/store/globalStore', () => ({
useGlobalStore: () => ({ history_type: 'list' }),
}));
const proxyFetchDeleteMock = vi.fn().mockResolvedValue({ code: 0 });
vi.mock('@/api/http', () => ({
proxyFetchDelete: (...args: any[]) => proxyFetchDeleteMock(...args),
}));
vi.mock('@/store/authStore', () => ({
getAuthStore: () => ({ email: 'test@example.com' }),
}));
vi.mock('@/service/historyApi', () => ({
fetchHistoryTasks: (setter: (tasks: any[]) => void) => {
setter([
{
id: '1',
task_id: 'task-1',
project_id: 'project-1',
question: 'My history item',
},
]);
},
}));
vi.mock('@/components/ui/command', () => ({
CommandDialog: ({ open, children }: any) =>
open ? <div data-testid="cmd-dialog">{children}</div> : null,
CommandEmpty: ({ children }: any) => <div>{children}</div>,
CommandGroup: ({ children }: any) => <div>{children}</div>,
CommandInput: (props: any) => <input aria-label="command-input" {...props} />,
CommandItem: ({ children, onSelect }: any) => (
<div role="button" tabIndex={0} onClick={() => onSelect?.('')}>
{children}
</div>
),
CommandList: ({ children }: any) => <div>{children}</div>,
CommandSeparator: () => <hr />,
}));
vi.mock('../../../src/components/ui/button', () => ({
Button: ({ children, onClick, ...rest }: any) => (
<button onClick={onClick} {...rest}>
{children}
</button>
),
}));
vi.mock('../../../src/components/ui/dialog', () => ({
DialogTitle: ({ children }: any) => <div>{children}</div>,
}));
vi.mock('@radix-ui/react-visually-hidden', () => ({
VisuallyHidden: ({ children }: any) => <span>{children}</span>,
}));
vi.mock('lucide-react', () => ({
Search: (props: any) => <div data-testid="search-icon" {...props} />,
ScanFace: (props: any) => <div data-testid="scanface-icon" {...props} />,
Trash2: (props: any) => <div data-testid="trash-icon" {...props} />,
}));
vi.mock('@/components/GroupedHistoryView', () => ({
default: () => <div data-testid="grouped-history-view" />,
}));
vi.mock('@/lib', () => ({
replayProject: vi.fn(),
}));
describe('SearchHistoryDialog', () => {
beforeEach(() => {
vi.clearAllMocks();
(window as any).ipcRenderer = {
invoke: vi.fn().mockResolvedValue(undefined),
};
});
it('deletes a history item from list view', async () => {
const user = userEvent.setup();
render(<SearchHistoryDialog />);
// Open dialog
await user.click(screen.getByRole('button'));
// History item should appear
await waitFor(() => {
expect(screen.getByText('My history item')).toBeInTheDocument();
});
// Click delete button
const deleteButton = screen.getByRole('button', { name: 'Delete history' });
await user.click(deleteButton);
await waitFor(() => {
expect(proxyFetchDeleteMock).toHaveBeenCalledWith('/api/chat/history/1');
});
expect((window as any).ipcRenderer.invoke).toHaveBeenCalledWith(
'delete-task-files',
'test@example.com',
'task-1',
'project-1'
);
await waitFor(() => {
expect(screen.queryByText('My history item')).not.toBeInTheDocument();
});
});
});

View file

@ -1,84 +0,0 @@
// ========= Copyright 2025-2026 @ Eigent.ai All Rights Reserved. =========
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// ========= Copyright 2025-2026 @ Eigent.ai All Rights Reserved. =========
import { beforeEach, describe, expect, it, vi } from 'vitest';
import {
loginByStackToken,
loginByStackWithAutoCreate,
} from '../../../src/service/stackAuthApi';
vi.mock('@/api/http', () => ({
proxyFetchPost: vi.fn(),
}));
describe('stackAuthApi', () => {
beforeEach(() => {
vi.clearAllMocks();
});
it('falls back to signup when login returns user not found', async () => {
const { proxyFetchPost } = await import('@/api/http');
vi.mocked(proxyFetchPost)
.mockResolvedValueOnce({ code: 1, text: 'User not found' })
.mockResolvedValueOnce({ code: 0, token: 't', email: 'e@example.com' });
const res = await loginByStackWithAutoCreate('stack-token');
expect(res.code).toBe(0);
expect(vi.mocked(proxyFetchPost)).toHaveBeenCalledTimes(2);
const firstUrl = vi.mocked(proxyFetchPost).mock.calls[0][0] as string;
const secondUrl = vi.mocked(proxyFetchPost).mock.calls[1][0] as string;
expect(firstUrl).toContain('/api/login-by_stack?');
expect(firstUrl).toContain('type=login');
expect(secondUrl).toContain('type=signup');
});
it('does not fall back to signup for account/password error', async () => {
const { proxyFetchPost } = await import('@/api/http');
vi.mocked(proxyFetchPost).mockResolvedValueOnce({
code: 10,
text: 'Account or password error',
});
const res = await loginByStackWithAutoCreate('stack-token');
expect(res.code).toBe(10);
expect(res.text).toBe('Account or password error');
expect(vi.mocked(proxyFetchPost)).toHaveBeenCalledTimes(1);
const firstUrl = vi.mocked(proxyFetchPost).mock.calls[0][0] as string;
expect(firstUrl).toContain('/api/login-by_stack?');
expect(firstUrl).toContain('type=login');
});
it('includes invite_code in query when provided', async () => {
const { proxyFetchPost } = await import('@/api/http');
vi.mocked(proxyFetchPost).mockResolvedValueOnce({ code: 0 });
await loginByStackToken({
token: 'stack-token',
type: 'signup',
inviteCode: 'INV123',
});
const url = vi.mocked(proxyFetchPost).mock.calls[0][0] as string;
expect(url).toContain('invite_code=INV123');
});
});

View file

@ -45,7 +45,9 @@ export default defineConfig(({ command, mode }) => {
entry: 'electron/main/index.ts',
onstart(args) {
if (process.env.VSCODE_DEBUG) {
console.log('[startup] Electron App');
console.log(
/* For `.vscode/.debug.script.mjs` */ '[startup] Electron App'
);
} else {
args.startup();
}