mirror of
https://github.com/tinyhumansai/openhuman.git
synced 2026-07-27 21:08:00 +00:00
fix(settings): prompt for managed embeddings sign-in (#3858)
Co-authored-by: Steven Enamakel <enamakel@tinyhumans.ai>
This commit is contained in:
co-authored by
Steven Enamakel
parent
9e5d0329bf
commit
a15da11c5f
@@ -8,6 +8,7 @@
|
||||
import { useCallback, useEffect, useState } from 'react';
|
||||
|
||||
import { useT } from '../../../lib/i18n/I18nContext';
|
||||
import { useCoreState } from '../../../providers/CoreStateProvider';
|
||||
import {
|
||||
clearEmbeddingsApiKey,
|
||||
type EmbeddingProviderEntry,
|
||||
@@ -18,6 +19,7 @@ import {
|
||||
testEmbeddingsConnection,
|
||||
updateEmbeddingsSettings,
|
||||
} from '../../../services/api/embeddingsApi';
|
||||
import { isLocalSessionToken } from '../../../utils/localSession';
|
||||
import PanelPage from '../../layout/PanelPage';
|
||||
import Button from '../../ui/Button';
|
||||
import SettingsBackButton from '../components/SettingsBackButton';
|
||||
@@ -38,6 +40,16 @@ type Status =
|
||||
| { kind: 'saved' }
|
||||
| { kind: 'error'; message: string };
|
||||
|
||||
function isBackendSessionError(message: string | undefined): boolean {
|
||||
const text = message ?? '';
|
||||
return (
|
||||
/no backend session/i.test(text) ||
|
||||
/SESSION_EXPIRED/i.test(text) ||
|
||||
/session expired/i.test(text) ||
|
||||
(/invalid token/i.test(text) && /(401|unauthorized)/i.test(text))
|
||||
);
|
||||
}
|
||||
|
||||
interface EmbeddingsPanelProps {
|
||||
embedded?: boolean;
|
||||
}
|
||||
@@ -45,9 +57,12 @@ interface EmbeddingsPanelProps {
|
||||
const EmbeddingsPanel = ({ embedded = false }: EmbeddingsPanelProps = {}) => {
|
||||
const { t } = useT();
|
||||
const { navigateBack } = useSettingsNavigation();
|
||||
const { snapshot, clearSession } = useCoreState();
|
||||
const isLocalSession = isLocalSessionToken(snapshot.sessionToken);
|
||||
|
||||
const [settings, setSettings] = useState<EmbeddingsSettings | null>(null);
|
||||
const [status, setStatus] = useState<Status>({ kind: 'loading' });
|
||||
const [managedSessionMissing, setManagedSessionMissing] = useState(false);
|
||||
|
||||
// Setup popup state
|
||||
const [setupProvider, setSetupProvider] = useState<EmbeddingProviderEntry | null>(null);
|
||||
@@ -110,10 +125,22 @@ const EmbeddingsPanel = ({ embedded = false }: EmbeddingsPanelProps = {}) => {
|
||||
const currentModels = currentEntry?.models ?? [];
|
||||
const currentModel = currentModels.find(m => m.id === settings.model) ?? currentModels[0];
|
||||
const allowedDims = currentModel?.allowed_dimensions ?? [];
|
||||
const managedLoginMessage = t('settings.embeddings.managedLoginRequired');
|
||||
const managedRequiresLogin = isLocalSession && selectedProvider === 'managed';
|
||||
const showManagedLoginPrompt =
|
||||
(selectedProvider === 'managed' && (managedRequiresLogin || managedSessionMissing)) ||
|
||||
(isLocalSession && managedSessionMissing);
|
||||
|
||||
function handleProviderClick(entry: EmbeddingProviderEntry) {
|
||||
if (entry.slug !== 'managed') setManagedSessionMissing(false);
|
||||
if (entry.slug === selectedProvider) return;
|
||||
|
||||
if (entry.slug === 'managed' && isLocalSession) {
|
||||
setManagedSessionMissing(true);
|
||||
setStatus({ kind: 'error', message: managedLoginMessage });
|
||||
return;
|
||||
}
|
||||
|
||||
if (entry.slug === 'custom') {
|
||||
// For custom, open setup popup to enter endpoint
|
||||
setSetupProvider(entry);
|
||||
@@ -143,6 +170,7 @@ const EmbeddingsPanel = ({ embedded = false }: EmbeddingsPanelProps = {}) => {
|
||||
const newModel = model ?? defaultModel?.id ?? settings!.model;
|
||||
const newDims = dims ?? defaultModel?.default_dimensions ?? settings!.dimensions;
|
||||
|
||||
if (slug !== 'managed') setManagedSessionMissing(false);
|
||||
setStatus({ kind: 'saving' });
|
||||
try {
|
||||
const result = await updateEmbeddingsSettings({
|
||||
@@ -338,12 +366,25 @@ const EmbeddingsPanel = ({ embedded = false }: EmbeddingsPanelProps = {}) => {
|
||||
try {
|
||||
const result = await testEmbeddingsConnection();
|
||||
if (result.success) {
|
||||
setManagedSessionMissing(false);
|
||||
setStatus({ kind: 'saved' });
|
||||
} else {
|
||||
setStatus({ kind: 'error', message: result.error ?? 'Test failed' });
|
||||
const message = result.error ?? t('settings.embeddings.connectionTestFailed');
|
||||
if (selectedProvider === 'managed' && isBackendSessionError(message)) {
|
||||
setManagedSessionMissing(true);
|
||||
setStatus({ kind: 'error', message: managedLoginMessage });
|
||||
} else {
|
||||
setStatus({ kind: 'error', message });
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
setStatus({ kind: 'error', message: err instanceof Error ? err.message : String(err) });
|
||||
const message = err instanceof Error ? err.message : String(err);
|
||||
if (selectedProvider === 'managed' && isBackendSessionError(message)) {
|
||||
setManagedSessionMissing(true);
|
||||
setStatus({ kind: 'error', message: managedLoginMessage });
|
||||
} else {
|
||||
setStatus({ kind: 'error', message });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -389,6 +430,11 @@ const EmbeddingsPanel = ({ embedded = false }: EmbeddingsPanelProps = {}) => {
|
||||
: t('settings.embeddings.statusNeedsKey')}
|
||||
</SettingsBadge>
|
||||
)}
|
||||
{isLocalSession && entry.slug === 'managed' && (
|
||||
<SettingsBadge variant="warning">
|
||||
{t('settings.embeddings.requiresSignIn')}
|
||||
</SettingsBadge>
|
||||
)}
|
||||
</span>
|
||||
<span className="block mt-0.5 text-xs text-neutral-500 dark:text-neutral-400">
|
||||
{entry.description}
|
||||
@@ -415,6 +461,29 @@ const EmbeddingsPanel = ({ embedded = false }: EmbeddingsPanelProps = {}) => {
|
||||
</div>
|
||||
</SettingsSection>
|
||||
|
||||
{showManagedLoginPrompt && (
|
||||
<div className="rounded-xl border border-amber-200 dark:border-amber-500/30 bg-amber-50 dark:bg-amber-900/10 p-3">
|
||||
<div className="flex flex-col gap-3 sm:flex-row sm:items-start sm:justify-between">
|
||||
<p className="text-xs text-amber-800 dark:text-amber-200 leading-relaxed">
|
||||
{t('settings.embeddings.managedBannerIntro')}{' '}
|
||||
{isLocalSession
|
||||
? t('settings.embeddings.managedBannerLocalSession')
|
||||
: t('settings.embeddings.managedBannerRemoteSession')}
|
||||
</p>
|
||||
<Button
|
||||
type="button"
|
||||
variant="secondary"
|
||||
size="xs"
|
||||
className="shrink-0"
|
||||
onClick={() => void clearSession()}>
|
||||
{isLocalSession
|
||||
? t('settings.exitLocalSession')
|
||||
: t('settings.embeddings.signInAgain')}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Vector search disabled notice */}
|
||||
{selectedProvider === 'none' && (
|
||||
<div className="rounded-xl border border-amber-200 dark:border-amber-500/30 bg-amber-50 dark:bg-amber-900/10 p-3">
|
||||
@@ -487,7 +556,7 @@ const EmbeddingsPanel = ({ embedded = false }: EmbeddingsPanelProps = {}) => {
|
||||
variant="secondary"
|
||||
size="xs"
|
||||
onClick={() => void handleTestConnection()}
|
||||
disabled={selectedProvider === 'none'}>
|
||||
disabled={selectedProvider === 'none' || managedRequiresLogin}>
|
||||
{t('settings.embeddings.testConnection')}
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
import { fireEvent, screen, waitFor } from '@testing-library/react';
|
||||
import { beforeEach, describe, expect, it, vi } from 'vitest';
|
||||
|
||||
import { setCoreStateSnapshot } from '../../../../lib/coreState/store';
|
||||
import {
|
||||
clearEmbeddingsApiKey,
|
||||
type EmbeddingProviderEntry,
|
||||
@@ -73,9 +74,41 @@ const makeSettings = (overrides: Partial<EmbeddingsSettings> = {}): EmbeddingsSe
|
||||
...overrides,
|
||||
});
|
||||
|
||||
const setCoreSession = ({
|
||||
sessionToken = 'header.payload.remote',
|
||||
userId = 'u-1',
|
||||
profileId = 'p-1',
|
||||
}: { sessionToken?: string; userId?: string; profileId?: string | null } = {}) => {
|
||||
setCoreStateSnapshot({
|
||||
isBootstrapping: false,
|
||||
isReady: true,
|
||||
snapshot: {
|
||||
auth: { isAuthenticated: true, userId, user: null, profileId },
|
||||
sessionToken,
|
||||
currentUser: null,
|
||||
onboardingCompleted: true,
|
||||
chatOnboardingCompleted: true,
|
||||
analyticsEnabled: false,
|
||||
meetAutoOrchestratorHandoff: false,
|
||||
localState: { encryptionKey: null, onboardingTasks: null, keyringConsent: null },
|
||||
keyringStatus: {
|
||||
available: true,
|
||||
failureReason: null,
|
||||
activeMode: 'os_keyring',
|
||||
backendName: 'os',
|
||||
},
|
||||
runtime: { screenIntelligence: null, localAi: null, autocomplete: null, service: null },
|
||||
},
|
||||
teams: [],
|
||||
teamMembersById: {},
|
||||
teamInvitesById: {},
|
||||
});
|
||||
};
|
||||
|
||||
describe('EmbeddingsPanel', () => {
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
setCoreSession();
|
||||
vi.mocked(loadEmbeddingsSettings).mockResolvedValue(makeSettings());
|
||||
vi.mocked(updateEmbeddingsSettings).mockResolvedValue({
|
||||
provider: 'managed',
|
||||
@@ -101,6 +134,46 @@ describe('EmbeddingsPanel', () => {
|
||||
expect(screen.getByText('Custom')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('marks Managed embeddings as requiring OpenHuman sign-in for local sessions', async () => {
|
||||
setCoreSession({ sessionToken: 'header.payload.local', userId: 'local', profileId: null });
|
||||
|
||||
renderWithProviders(<EmbeddingsPanel />);
|
||||
|
||||
expect(await screen.findByText('Managed')).toBeInTheDocument();
|
||||
expect(screen.getByText(/requires OpenHuman sign-in/i)).toBeInTheDocument();
|
||||
expect(
|
||||
screen.getByText(/Managed embeddings route through the OpenHuman backend/i)
|
||||
).toBeInTheDocument();
|
||||
expect(screen.getByRole('button', { name: /Test Connection/i })).toBeDisabled();
|
||||
});
|
||||
|
||||
it('blocks switching to Managed embeddings during a local session', async () => {
|
||||
setCoreSession({ sessionToken: 'header.payload.local', userId: 'local', profileId: null });
|
||||
vi.mocked(loadEmbeddingsSettings).mockResolvedValue(
|
||||
makeSettings({
|
||||
provider: 'openai',
|
||||
model: 'openai-model-v1',
|
||||
providers: [
|
||||
makeProvider('managed', { requires_api_key: false }),
|
||||
makeProvider('openai', { requires_api_key: true, has_api_key: true }),
|
||||
],
|
||||
})
|
||||
);
|
||||
|
||||
renderWithProviders(<EmbeddingsPanel />);
|
||||
await screen.findByText('Managed');
|
||||
|
||||
fireEvent.click(screen.getByRole('radio', { name: /managed/i }));
|
||||
|
||||
await waitFor(() =>
|
||||
expect(screen.getByText(/Managed embeddings require OpenHuman sign-in/i)).toBeInTheDocument()
|
||||
);
|
||||
expect(
|
||||
screen.getByText(/Managed embeddings route through the OpenHuman backend/i)
|
||||
).toBeInTheDocument();
|
||||
expect(vi.mocked(updateEmbeddingsSettings)).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('shows loading state then settings', async () => {
|
||||
let resolveLoad!: (s: EmbeddingsSettings) => void;
|
||||
vi.mocked(loadEmbeddingsSettings).mockReturnValue(
|
||||
@@ -634,6 +707,52 @@ describe('EmbeddingsPanel', () => {
|
||||
await waitFor(() => expect(screen.getByText(/connection refused/i)).toBeInTheDocument());
|
||||
});
|
||||
|
||||
it.each([
|
||||
['missing backend session', 'No backend session for cloud embeddings: log in to OpenHuman'],
|
||||
['session-expired sentinel', 'SESSION_EXPIRED: backend session not active'],
|
||||
[
|
||||
'backend invalid token',
|
||||
'Embedding API error (401 Unauthorized): {"success":false,"error":"Invalid token"}',
|
||||
],
|
||||
])('turns Managed %s test failures into sign-in guidance', async (_case, error) => {
|
||||
const settings = makeSettings({
|
||||
provider: 'managed',
|
||||
providers: [
|
||||
makeProvider('managed', {
|
||||
requires_api_key: false,
|
||||
models: [
|
||||
{
|
||||
id: 'managed-model-v1',
|
||||
label: 'Managed Model v1',
|
||||
default_dimensions: 1536,
|
||||
allowed_dimensions: [1536],
|
||||
},
|
||||
],
|
||||
}),
|
||||
],
|
||||
});
|
||||
vi.mocked(loadEmbeddingsSettings).mockResolvedValue(settings);
|
||||
vi.mocked(testEmbeddingsConnection).mockResolvedValueOnce({
|
||||
success: false,
|
||||
provider: 'managed',
|
||||
model: 'managed-model-v1',
|
||||
error,
|
||||
});
|
||||
|
||||
renderWithProviders(<EmbeddingsPanel />);
|
||||
await screen.findByText('Managed');
|
||||
|
||||
fireEvent.click(await screen.findByRole('button', { name: /test connection/i }));
|
||||
|
||||
await waitFor(() =>
|
||||
expect(screen.getByText(/Managed embeddings require OpenHuman sign-in/i)).toBeInTheDocument()
|
||||
);
|
||||
expect(
|
||||
screen.getByText(/Managed embeddings route through the OpenHuman backend/i)
|
||||
).toBeInTheDocument();
|
||||
fireEvent.click(screen.getByRole('button', { name: /sign in again/i }));
|
||||
});
|
||||
|
||||
// ─── Model select (multiple catalog models) ───────────────────────────────
|
||||
|
||||
it('shows model select when provider has multiple models', async () => {
|
||||
|
||||
@@ -1144,6 +1144,16 @@ const messages: TranslationMap = {
|
||||
'settings.embeddings.providerAria': 'مزود التضمينات',
|
||||
'settings.embeddings.statusConfigured': 'تم التهيئة',
|
||||
'settings.embeddings.statusNeedsKey': 'يحتاج مفتاح API',
|
||||
'settings.embeddings.requiresSignIn': 'يتطلب تسجيل الدخول إلى OpenHuman',
|
||||
'settings.embeddings.managedLoginRequired':
|
||||
'تتطلب التضمينات المُدارة تسجيل الدخول إلى OpenHuman. سجّل الدخول لاستخدام خلفية OpenHuman.',
|
||||
'settings.embeddings.managedBannerIntro':
|
||||
'تمر التضمينات المُدارة عبر خلفية OpenHuman وتتطلب جلسة حساب OpenHuman.',
|
||||
'settings.embeddings.managedBannerLocalSession':
|
||||
'اخرج من الجلسة المحلية وسجّل الدخول لاستخدام هذا المزوّد، أو انتقل إلى مزوّد تضمينات محلي أو بمفتاحك الخاص.',
|
||||
'settings.embeddings.managedBannerRemoteSession':
|
||||
'سجّل الدخول مرة أخرى لتحديث جلسة OpenHuman، أو انتقل إلى مزوّد تضمينات محلي أو بمفتاحك الخاص.',
|
||||
'settings.embeddings.signInAgain': 'تسجيل الدخول مرة أخرى',
|
||||
'settings.embeddings.apiKeyLabel': 'مفتاح API لـ {provider}',
|
||||
'settings.embeddings.placeholderStored': '•••••••• (مخزن)',
|
||||
'settings.embeddings.placeholderKey': 'الصق مفتاح API الخاص بك…',
|
||||
@@ -1161,6 +1171,7 @@ const messages: TranslationMap = {
|
||||
'settings.embeddings.testConnection': 'اختبار الاتصال',
|
||||
'settings.embeddings.testing': 'جارٍ الاختبار…',
|
||||
'settings.embeddings.testSuccess': 'متصل — {dims} بُعد',
|
||||
'settings.embeddings.connectionTestFailed': 'فشل الاختبار',
|
||||
'settings.embeddings.testFailed': 'فشل: {error}',
|
||||
'settings.embeddings.saving': 'جارٍ الحفظ…',
|
||||
'settings.embeddings.saved': 'تم الحفظ.',
|
||||
|
||||
@@ -1163,6 +1163,16 @@ const messages: TranslationMap = {
|
||||
'settings.embeddings.providerAria': 'এমবেডিং প্রদানকারী',
|
||||
'settings.embeddings.statusConfigured': 'কনফিগার করা হয়েছে',
|
||||
'settings.embeddings.statusNeedsKey': 'API কী প্রয়োজন',
|
||||
'settings.embeddings.requiresSignIn': 'OpenHuman-এ সাইন-ইন প্রয়োজন',
|
||||
'settings.embeddings.managedLoginRequired':
|
||||
'ম্যানেজড এমবেডিংসের জন্য OpenHuman সাইন-ইন প্রয়োজন। OpenHuman ব্যাকএন্ড ব্যবহার করতে সাইন ইন করুন।',
|
||||
'settings.embeddings.managedBannerIntro':
|
||||
'ম্যানেজড এমবেডিংস OpenHuman ব্যাকএন্ডের মাধ্যমে চলে এবং একটি OpenHuman অ্যাকাউন্ট সেশন প্রয়োজন।',
|
||||
'settings.embeddings.managedBannerLocalSession':
|
||||
'এই প্রোভাইডার ব্যবহার করতে স্থানীয় সেশন থেকে বেরিয়ে সাইন ইন করুন, অথবা স্থানীয়/নিজস্ব-কী এমবেডিং প্রোভাইডারে যান।',
|
||||
'settings.embeddings.managedBannerRemoteSession':
|
||||
'OpenHuman সেশন রিফ্রেশ করতে আবার সাইন ইন করুন, অথবা স্থানীয়/নিজস্ব-কী এমবেডিং প্রোভাইডারে যান।',
|
||||
'settings.embeddings.signInAgain': 'আবার সাইন ইন করুন',
|
||||
'settings.embeddings.apiKeyLabel': '{provider} API কী',
|
||||
'settings.embeddings.placeholderStored': '•••••••• (সঞ্চিত)',
|
||||
'settings.embeddings.placeholderKey': 'আপনার API কী পেস্ট করুন…',
|
||||
@@ -1180,6 +1190,7 @@ const messages: TranslationMap = {
|
||||
'settings.embeddings.testConnection': 'সংযোগ পরীক্ষা',
|
||||
'settings.embeddings.testing': 'পরীক্ষা হচ্ছে…',
|
||||
'settings.embeddings.testSuccess': 'সংযুক্ত — {dims} মাত্রা',
|
||||
'settings.embeddings.connectionTestFailed': 'পরীক্ষা ব্যর্থ হয়েছে',
|
||||
'settings.embeddings.testFailed': 'ব্যর্থ: {error}',
|
||||
'settings.embeddings.saving': 'সংরক্ষণ হচ্ছে…',
|
||||
'settings.embeddings.saved': 'সংরক্ষিত।',
|
||||
|
||||
@@ -1195,6 +1195,16 @@ const messages: TranslationMap = {
|
||||
'settings.embeddings.providerAria': 'Embedding-Anbieter',
|
||||
'settings.embeddings.statusConfigured': 'Konfiguriert',
|
||||
'settings.embeddings.statusNeedsKey': 'API-Schlüssel benötigt',
|
||||
'settings.embeddings.requiresSignIn': 'OpenHuman-Anmeldung erforderlich',
|
||||
'settings.embeddings.managedLoginRequired':
|
||||
'Verwaltete Einbettungen erfordern eine OpenHuman-Anmeldung. Melde dich an, um das OpenHuman-Backend zu verwenden.',
|
||||
'settings.embeddings.managedBannerIntro':
|
||||
'Verwaltete Einbettungen laufen über das OpenHuman-Backend und erfordern eine OpenHuman-Kontositzung.',
|
||||
'settings.embeddings.managedBannerLocalSession':
|
||||
'Beende die lokale Sitzung und melde dich an, um diesen Anbieter zu verwenden, oder wechsle zu einem lokalen oder eigenen Einbettungsanbieter.',
|
||||
'settings.embeddings.managedBannerRemoteSession':
|
||||
'Melde dich erneut an, um deine OpenHuman-Sitzung zu aktualisieren, oder wechsle zu einem lokalen oder eigenen Einbettungsanbieter.',
|
||||
'settings.embeddings.signInAgain': 'Erneut anmelden',
|
||||
'settings.embeddings.apiKeyLabel': '{provider} API-Schlüssel',
|
||||
'settings.embeddings.placeholderStored': '•••••••• (gespeichert)',
|
||||
'settings.embeddings.placeholderKey': 'API-Schlüssel einfügen…',
|
||||
@@ -1213,6 +1223,7 @@ const messages: TranslationMap = {
|
||||
'settings.embeddings.testConnection': 'Verbindung testen',
|
||||
'settings.embeddings.testing': 'Wird getestet…',
|
||||
'settings.embeddings.testSuccess': 'Verbunden — {dims} Dimensionen',
|
||||
'settings.embeddings.connectionTestFailed': 'Test fehlgeschlagen',
|
||||
'settings.embeddings.testFailed': 'Fehlgeschlagen: {error}',
|
||||
'settings.embeddings.saving': 'Wird gespeichert…',
|
||||
'settings.embeddings.saved': 'Gespeichert.',
|
||||
|
||||
@@ -1511,6 +1511,16 @@ const en: TranslationMap = {
|
||||
'settings.embeddings.providerAria': 'Embedding provider',
|
||||
'settings.embeddings.statusConfigured': 'Configured',
|
||||
'settings.embeddings.statusNeedsKey': 'Needs API key',
|
||||
'settings.embeddings.requiresSignIn': 'Requires OpenHuman sign-in',
|
||||
'settings.embeddings.managedLoginRequired':
|
||||
'Managed embeddings require OpenHuman sign-in. Sign in to use the OpenHuman backend.',
|
||||
'settings.embeddings.managedBannerIntro':
|
||||
'Managed embeddings route through the OpenHuman backend and require an OpenHuman account session.',
|
||||
'settings.embeddings.managedBannerLocalSession':
|
||||
'Exit local session and sign in to use this provider, or switch to a local or bring-your-own embeddings provider.',
|
||||
'settings.embeddings.managedBannerRemoteSession':
|
||||
'Sign in again to refresh your OpenHuman session, or switch to a local or bring-your-own embeddings provider.',
|
||||
'settings.embeddings.signInAgain': 'Sign in again',
|
||||
'settings.embeddings.apiKeyLabel': '{provider} API key',
|
||||
'settings.embeddings.placeholderStored': '•••••••• (stored)',
|
||||
'settings.embeddings.placeholderKey': 'Paste your API key…',
|
||||
@@ -1528,6 +1538,7 @@ const en: TranslationMap = {
|
||||
'settings.embeddings.testConnection': 'Test connection',
|
||||
'settings.embeddings.testing': 'Testing…',
|
||||
'settings.embeddings.testSuccess': 'Connected — {dims} dimensions',
|
||||
'settings.embeddings.connectionTestFailed': 'Test failed',
|
||||
'settings.embeddings.testFailed': 'Failed: {error}',
|
||||
'settings.embeddings.saving': 'Saving…',
|
||||
'settings.embeddings.saved': 'Saved.',
|
||||
|
||||
@@ -1190,6 +1190,16 @@ const messages: TranslationMap = {
|
||||
'settings.embeddings.providerAria': 'Proveedor de embeddings',
|
||||
'settings.embeddings.statusConfigured': 'Configurado',
|
||||
'settings.embeddings.statusNeedsKey': 'Necesita clave API',
|
||||
'settings.embeddings.requiresSignIn': 'Requiere iniciar sesión en OpenHuman',
|
||||
'settings.embeddings.managedLoginRequired':
|
||||
'Los embeddings administrados requieren iniciar sesión en OpenHuman. Inicia sesión para usar el backend de OpenHuman.',
|
||||
'settings.embeddings.managedBannerIntro':
|
||||
'Los embeddings administrados se enrutan por el backend de OpenHuman y requieren una sesión de cuenta de OpenHuman.',
|
||||
'settings.embeddings.managedBannerLocalSession':
|
||||
'Sal de la sesión local e inicia sesión para usar este proveedor, o cambia a un proveedor de embeddings local o con tu propia clave.',
|
||||
'settings.embeddings.managedBannerRemoteSession':
|
||||
'Vuelve a iniciar sesión para actualizar tu sesión de OpenHuman, o cambia a un proveedor de embeddings local o con tu propia clave.',
|
||||
'settings.embeddings.signInAgain': 'Volver a iniciar sesión',
|
||||
'settings.embeddings.apiKeyLabel': 'Clave API de {provider}',
|
||||
'settings.embeddings.placeholderStored': '•••••••• (almacenado)',
|
||||
'settings.embeddings.placeholderKey': 'Pega tu clave API…',
|
||||
@@ -1207,6 +1217,7 @@ const messages: TranslationMap = {
|
||||
'settings.embeddings.testConnection': 'Probar conexión',
|
||||
'settings.embeddings.testing': 'Probando…',
|
||||
'settings.embeddings.testSuccess': 'Conectado — {dims} dimensiones',
|
||||
'settings.embeddings.connectionTestFailed': 'La prueba falló',
|
||||
'settings.embeddings.testFailed': 'Fallido: {error}',
|
||||
'settings.embeddings.saving': 'Guardando…',
|
||||
'settings.embeddings.saved': 'Guardado.',
|
||||
|
||||
@@ -1195,6 +1195,16 @@ const messages: TranslationMap = {
|
||||
'settings.embeddings.providerAria': "Fournisseur d'embeddings",
|
||||
'settings.embeddings.statusConfigured': 'Configuré',
|
||||
'settings.embeddings.statusNeedsKey': 'Clé API requise',
|
||||
'settings.embeddings.requiresSignIn': 'Connexion OpenHuman requise',
|
||||
'settings.embeddings.managedLoginRequired':
|
||||
'Les embeddings gérés nécessitent une connexion OpenHuman. Connectez-vous pour utiliser le backend OpenHuman.',
|
||||
'settings.embeddings.managedBannerIntro':
|
||||
'Les embeddings gérés passent par le backend OpenHuman et nécessitent une session de compte OpenHuman.',
|
||||
'settings.embeddings.managedBannerLocalSession':
|
||||
'Quittez la session locale et connectez-vous pour utiliser ce fournisseur, ou passez à un fournisseur local ou avec votre propre clé.',
|
||||
'settings.embeddings.managedBannerRemoteSession':
|
||||
'Reconnectez-vous pour actualiser votre session OpenHuman, ou passez à un fournisseur local ou avec votre propre clé.',
|
||||
'settings.embeddings.signInAgain': 'Se reconnecter',
|
||||
'settings.embeddings.apiKeyLabel': 'Clé API {provider}',
|
||||
'settings.embeddings.placeholderStored': '•••••••• (stocké)',
|
||||
'settings.embeddings.placeholderKey': 'Collez votre clé API…',
|
||||
@@ -1213,6 +1223,7 @@ const messages: TranslationMap = {
|
||||
'settings.embeddings.testConnection': 'Tester la connexion',
|
||||
'settings.embeddings.testing': 'Test en cours…',
|
||||
'settings.embeddings.testSuccess': 'Connecté — {dims} dimensions',
|
||||
'settings.embeddings.connectionTestFailed': 'Test échoué',
|
||||
'settings.embeddings.testFailed': 'Échec : {error}',
|
||||
'settings.embeddings.saving': 'Enregistrement…',
|
||||
'settings.embeddings.saved': 'Enregistré.',
|
||||
|
||||
@@ -1160,6 +1160,16 @@ const messages: TranslationMap = {
|
||||
'settings.embeddings.providerAria': 'एम्बेडिंग प्रदाता',
|
||||
'settings.embeddings.statusConfigured': 'कॉन्फ़िगर किया गया',
|
||||
'settings.embeddings.statusNeedsKey': 'API कुंजी चाहिए',
|
||||
'settings.embeddings.requiresSignIn': 'OpenHuman साइन-इन आवश्यक',
|
||||
'settings.embeddings.managedLoginRequired':
|
||||
'प्रबंधित एम्बेडिंग के लिए OpenHuman साइन-इन आवश्यक है। OpenHuman बैकएंड का उपयोग करने के लिए साइन इन करें।',
|
||||
'settings.embeddings.managedBannerIntro':
|
||||
'प्रबंधित एम्बेडिंग OpenHuman बैकएंड से होकर चलते हैं और OpenHuman खाता सत्र की आवश्यकता होती है।',
|
||||
'settings.embeddings.managedBannerLocalSession':
|
||||
'इस प्रदाता का उपयोग करने के लिए स्थानीय सत्र से बाहर निकलें और साइन इन करें, या स्थानीय/अपनी-कुंजी वाले एम्बेडिंग प्रदाता पर स्विच करें।',
|
||||
'settings.embeddings.managedBannerRemoteSession':
|
||||
'अपने OpenHuman सत्र को रीफ़्रेश करने के लिए फिर से साइन इन करें, या स्थानीय/अपनी-कुंजी वाले एम्बेडिंग प्रदाता पर स्विच करें।',
|
||||
'settings.embeddings.signInAgain': 'फिर से साइन इन करें',
|
||||
'settings.embeddings.apiKeyLabel': '{provider} API कुंजी',
|
||||
'settings.embeddings.placeholderStored': '•••••••• (संग्रहीत)',
|
||||
'settings.embeddings.placeholderKey': 'अपनी API कुंजी पेस्ट करें…',
|
||||
@@ -1178,6 +1188,7 @@ const messages: TranslationMap = {
|
||||
'settings.embeddings.testConnection': 'कनेक्शन परीक्षण',
|
||||
'settings.embeddings.testing': 'परीक्षण हो रहा है…',
|
||||
'settings.embeddings.testSuccess': 'कनेक्ट — {dims} आयाम',
|
||||
'settings.embeddings.connectionTestFailed': 'परीक्षण विफल',
|
||||
'settings.embeddings.testFailed': 'विफल: {error}',
|
||||
'settings.embeddings.saving': 'सहेजा जा रहा है…',
|
||||
'settings.embeddings.saved': 'सहेजा गया।',
|
||||
|
||||
@@ -1168,6 +1168,16 @@ const messages: TranslationMap = {
|
||||
'settings.embeddings.providerAria': 'Penyedia embedding',
|
||||
'settings.embeddings.statusConfigured': 'Dikonfigurasi',
|
||||
'settings.embeddings.statusNeedsKey': 'Perlu kunci API',
|
||||
'settings.embeddings.requiresSignIn': 'Memerlukan masuk OpenHuman',
|
||||
'settings.embeddings.managedLoginRequired':
|
||||
'Embedding terkelola memerlukan masuk OpenHuman. Masuk untuk menggunakan backend OpenHuman.',
|
||||
'settings.embeddings.managedBannerIntro':
|
||||
'Embedding terkelola dirutekan melalui backend OpenHuman dan memerlukan sesi akun OpenHuman.',
|
||||
'settings.embeddings.managedBannerLocalSession':
|
||||
'Keluar dari sesi lokal dan masuk untuk menggunakan penyedia ini, atau beralih ke penyedia embedding lokal atau bawa-kunci-sendiri.',
|
||||
'settings.embeddings.managedBannerRemoteSession':
|
||||
'Masuk lagi untuk menyegarkan sesi OpenHuman Anda, atau beralih ke penyedia embedding lokal atau bawa-kunci-sendiri.',
|
||||
'settings.embeddings.signInAgain': 'Masuk lagi',
|
||||
'settings.embeddings.apiKeyLabel': 'Kunci API {provider}',
|
||||
'settings.embeddings.placeholderStored': '•••••••• (disimpan)',
|
||||
'settings.embeddings.placeholderKey': 'Tempel kunci API Anda…',
|
||||
@@ -1185,6 +1195,7 @@ const messages: TranslationMap = {
|
||||
'settings.embeddings.testConnection': 'Uji koneksi',
|
||||
'settings.embeddings.testing': 'Menguji…',
|
||||
'settings.embeddings.testSuccess': 'Terhubung — {dims} dimensi',
|
||||
'settings.embeddings.connectionTestFailed': 'Pengujian gagal',
|
||||
'settings.embeddings.testFailed': 'Gagal: {error}',
|
||||
'settings.embeddings.saving': 'Menyimpan…',
|
||||
'settings.embeddings.saved': 'Tersimpan.',
|
||||
|
||||
@@ -1186,6 +1186,16 @@ const messages: TranslationMap = {
|
||||
'settings.embeddings.providerAria': 'Fornitore di embeddings',
|
||||
'settings.embeddings.statusConfigured': 'Configurato',
|
||||
'settings.embeddings.statusNeedsKey': 'Chiave API necessaria',
|
||||
'settings.embeddings.requiresSignIn': 'Accesso OpenHuman richiesto',
|
||||
'settings.embeddings.managedLoginRequired':
|
||||
'Gli embedding gestiti richiedono l’accesso a OpenHuman. Accedi per usare il backend OpenHuman.',
|
||||
'settings.embeddings.managedBannerIntro':
|
||||
'Gli embedding gestiti passano dal backend OpenHuman e richiedono una sessione account OpenHuman.',
|
||||
'settings.embeddings.managedBannerLocalSession':
|
||||
'Esci dalla sessione locale e accedi per usare questo provider, oppure passa a un provider di embedding locale o con chiave personale.',
|
||||
'settings.embeddings.managedBannerRemoteSession':
|
||||
'Accedi di nuovo per aggiornare la sessione OpenHuman, oppure passa a un provider di embedding locale o con chiave personale.',
|
||||
'settings.embeddings.signInAgain': 'Accedi di nuovo',
|
||||
'settings.embeddings.apiKeyLabel': 'Chiave API {provider}',
|
||||
'settings.embeddings.placeholderStored': '•••••••• (memorizzato)',
|
||||
'settings.embeddings.placeholderKey': 'Incolla la tua chiave API…',
|
||||
@@ -1204,6 +1214,7 @@ const messages: TranslationMap = {
|
||||
'settings.embeddings.testConnection': 'Testa connessione',
|
||||
'settings.embeddings.testing': 'Test in corso…',
|
||||
'settings.embeddings.testSuccess': 'Connesso — {dims} dimensioni',
|
||||
'settings.embeddings.connectionTestFailed': 'Test non riuscito',
|
||||
'settings.embeddings.testFailed': 'Fallito: {error}',
|
||||
'settings.embeddings.saving': 'Salvataggio…',
|
||||
'settings.embeddings.saved': 'Salvato.',
|
||||
|
||||
@@ -1161,6 +1161,16 @@ const messages: TranslationMap = {
|
||||
'settings.embeddings.providerAria': '임베딩 제공자',
|
||||
'settings.embeddings.statusConfigured': '구성됨',
|
||||
'settings.embeddings.statusNeedsKey': 'API 키 필요',
|
||||
'settings.embeddings.requiresSignIn': 'OpenHuman 로그인 필요',
|
||||
'settings.embeddings.managedLoginRequired':
|
||||
'관리형 임베딩을 사용하려면 OpenHuman 로그인이 필요합니다. OpenHuman 백엔드를 사용하려면 로그인하세요.',
|
||||
'settings.embeddings.managedBannerIntro':
|
||||
'관리형 임베딩은 OpenHuman 백엔드를 통해 라우팅되며 OpenHuman 계정 세션이 필요합니다.',
|
||||
'settings.embeddings.managedBannerLocalSession':
|
||||
'이 공급자를 사용하려면 로컬 세션을 종료하고 로그인하거나, 로컬 또는 자체 키 임베딩 공급자로 전환하세요.',
|
||||
'settings.embeddings.managedBannerRemoteSession':
|
||||
'OpenHuman 세션을 새로 고치려면 다시 로그인하거나, 로컬 또는 자체 키 임베딩 공급자로 전환하세요.',
|
||||
'settings.embeddings.signInAgain': '다시 로그인',
|
||||
'settings.embeddings.apiKeyLabel': '{provider} API 키',
|
||||
'settings.embeddings.placeholderStored': '•••••••(저장됨)',
|
||||
'settings.embeddings.placeholderKey': 'API 키를 붙여넣으세요…',
|
||||
@@ -1178,6 +1188,7 @@ const messages: TranslationMap = {
|
||||
'settings.embeddings.testConnection': '연결 테스트',
|
||||
'settings.embeddings.testing': '테스트 중…',
|
||||
'settings.embeddings.testSuccess': '연결됨 — {dims} 차원',
|
||||
'settings.embeddings.connectionTestFailed': '테스트 실패',
|
||||
'settings.embeddings.testFailed': '실패: {error}',
|
||||
'settings.embeddings.saving': '저장 중…',
|
||||
'settings.embeddings.saved': '저장됨.',
|
||||
|
||||
@@ -1180,6 +1180,16 @@ const messages: TranslationMap = {
|
||||
'settings.embeddings.providerAria': 'Dostawca embeddings',
|
||||
'settings.embeddings.statusConfigured': 'Skonfigurowano',
|
||||
'settings.embeddings.statusNeedsKey': 'Wymaga klucza API',
|
||||
'settings.embeddings.requiresSignIn': 'Wymagane logowanie do OpenHuman',
|
||||
'settings.embeddings.managedLoginRequired':
|
||||
'Zarządzane osadzania wymagają logowania do OpenHuman. Zaloguj się, aby użyć backendu OpenHuman.',
|
||||
'settings.embeddings.managedBannerIntro':
|
||||
'Zarządzane osadzania są kierowane przez backend OpenHuman i wymagają sesji konta OpenHuman.',
|
||||
'settings.embeddings.managedBannerLocalSession':
|
||||
'Zakończ sesję lokalną i zaloguj się, aby użyć tego dostawcy, albo przełącz się na lokalnego dostawcę osadzań lub dostawcę z własnym kluczem.',
|
||||
'settings.embeddings.managedBannerRemoteSession':
|
||||
'Zaloguj się ponownie, aby odświeżyć sesję OpenHuman, albo przełącz się na lokalnego dostawcę osadzań lub dostawcę z własnym kluczem.',
|
||||
'settings.embeddings.signInAgain': 'Zaloguj się ponownie',
|
||||
'settings.embeddings.apiKeyLabel': 'Klucz API {provider}',
|
||||
'settings.embeddings.placeholderStored': '•••••••• (zapisane)',
|
||||
'settings.embeddings.placeholderKey': 'Wklej swój klucz API…',
|
||||
@@ -1197,6 +1207,7 @@ const messages: TranslationMap = {
|
||||
'settings.embeddings.testConnection': 'Przetestuj połączenie',
|
||||
'settings.embeddings.testing': 'Testowanie…',
|
||||
'settings.embeddings.testSuccess': 'Połączono — wymiarów: {dims}',
|
||||
'settings.embeddings.connectionTestFailed': 'Test nie powiódł się',
|
||||
'settings.embeddings.testFailed': 'Niepowodzenie: {error}',
|
||||
'settings.embeddings.saving': 'Zapisywanie…',
|
||||
'settings.embeddings.saved': 'Zapisano.',
|
||||
|
||||
@@ -1193,6 +1193,16 @@ const messages: TranslationMap = {
|
||||
'settings.embeddings.providerAria': 'Provedor de embeddings',
|
||||
'settings.embeddings.statusConfigured': 'Configurado',
|
||||
'settings.embeddings.statusNeedsKey': 'Precisa de chave API',
|
||||
'settings.embeddings.requiresSignIn': 'Requer login no OpenHuman',
|
||||
'settings.embeddings.managedLoginRequired':
|
||||
'Embeddings gerenciados requerem login no OpenHuman. Faça login para usar o backend do OpenHuman.',
|
||||
'settings.embeddings.managedBannerIntro':
|
||||
'Embeddings gerenciados passam pelo backend do OpenHuman e exigem uma sessão de conta OpenHuman.',
|
||||
'settings.embeddings.managedBannerLocalSession':
|
||||
'Saia da sessão local e faça login para usar este provedor, ou mude para um provedor de embeddings local ou com sua própria chave.',
|
||||
'settings.embeddings.managedBannerRemoteSession':
|
||||
'Faça login novamente para atualizar sua sessão OpenHuman, ou mude para um provedor de embeddings local ou com sua própria chave.',
|
||||
'settings.embeddings.signInAgain': 'Entrar novamente',
|
||||
'settings.embeddings.apiKeyLabel': 'Chave API {provider}',
|
||||
'settings.embeddings.placeholderStored': '•••••••• (armazenado)',
|
||||
'settings.embeddings.placeholderKey': 'Cole sua chave API…',
|
||||
@@ -1211,6 +1221,7 @@ const messages: TranslationMap = {
|
||||
'settings.embeddings.testConnection': 'Testar conexão',
|
||||
'settings.embeddings.testing': 'Testando…',
|
||||
'settings.embeddings.testSuccess': 'Conectado — {dims} dimensões',
|
||||
'settings.embeddings.connectionTestFailed': 'Teste falhou',
|
||||
'settings.embeddings.testFailed': 'Falhou: {error}',
|
||||
'settings.embeddings.saving': 'Salvando…',
|
||||
'settings.embeddings.saved': 'Salvo.',
|
||||
|
||||
@@ -1177,6 +1177,16 @@ const messages: TranslationMap = {
|
||||
'settings.embeddings.providerAria': 'Провайдер эмбеддингов',
|
||||
'settings.embeddings.statusConfigured': 'Настроено',
|
||||
'settings.embeddings.statusNeedsKey': 'Нужен API-ключ',
|
||||
'settings.embeddings.requiresSignIn': 'Требуется вход в OpenHuman',
|
||||
'settings.embeddings.managedLoginRequired':
|
||||
'Для управляемых эмбеддингов требуется вход в OpenHuman. Войдите, чтобы использовать backend OpenHuman.',
|
||||
'settings.embeddings.managedBannerIntro':
|
||||
'Управляемые эмбеддинги проходят через backend OpenHuman и требуют сеанс учетной записи OpenHuman.',
|
||||
'settings.embeddings.managedBannerLocalSession':
|
||||
'Выйдите из локального сеанса и войдите, чтобы использовать этого провайдера, или переключитесь на локального провайдера эмбеддингов либо провайдера с собственным ключом.',
|
||||
'settings.embeddings.managedBannerRemoteSession':
|
||||
'Войдите снова, чтобы обновить сеанс OpenHuman, или переключитесь на локального провайдера эмбеддингов либо провайдера с собственным ключом.',
|
||||
'settings.embeddings.signInAgain': 'Войти снова',
|
||||
'settings.embeddings.apiKeyLabel': 'API-ключ {provider}',
|
||||
'settings.embeddings.placeholderStored': '•••••••• (сохранено)',
|
||||
'settings.embeddings.placeholderKey': 'Вставьте API-ключ…',
|
||||
@@ -1195,6 +1205,7 @@ const messages: TranslationMap = {
|
||||
'settings.embeddings.testConnection': 'Проверить подключение',
|
||||
'settings.embeddings.testing': 'Проверка…',
|
||||
'settings.embeddings.testSuccess': 'Подключено — {dims} измерений',
|
||||
'settings.embeddings.connectionTestFailed': 'Проверка не удалась',
|
||||
'settings.embeddings.testFailed': 'Ошибка: {error}',
|
||||
'settings.embeddings.saving': 'Сохранение…',
|
||||
'settings.embeddings.saved': 'Сохранено.',
|
||||
|
||||
@@ -1110,6 +1110,16 @@ const messages: TranslationMap = {
|
||||
'settings.embeddings.providerAria': '嵌入提供商',
|
||||
'settings.embeddings.statusConfigured': '已配置',
|
||||
'settings.embeddings.statusNeedsKey': '需要 API 密钥',
|
||||
'settings.embeddings.requiresSignIn': '需要登录 OpenHuman',
|
||||
'settings.embeddings.managedLoginRequired':
|
||||
'托管嵌入需要登录 OpenHuman。请登录后使用 OpenHuman 后端。',
|
||||
'settings.embeddings.managedBannerIntro':
|
||||
'托管嵌入会通过 OpenHuman 后端,并且需要 OpenHuman 账号会话。',
|
||||
'settings.embeddings.managedBannerLocalSession':
|
||||
'退出本地会话并登录即可使用此提供商,或切换到本地/自带密钥的嵌入提供商。',
|
||||
'settings.embeddings.managedBannerRemoteSession':
|
||||
'重新登录以刷新 OpenHuman 会话,或切换到本地/自带密钥的嵌入提供商。',
|
||||
'settings.embeddings.signInAgain': '重新登录',
|
||||
'settings.embeddings.apiKeyLabel': '{provider} API 密钥',
|
||||
'settings.embeddings.placeholderStored': '••••••••(已存储)',
|
||||
'settings.embeddings.placeholderKey': '粘贴您的 API 密钥…',
|
||||
@@ -1127,6 +1137,7 @@ const messages: TranslationMap = {
|
||||
'settings.embeddings.testConnection': '测试连接',
|
||||
'settings.embeddings.testing': '测试中…',
|
||||
'settings.embeddings.testSuccess': '已连接 — {dims} 维度',
|
||||
'settings.embeddings.connectionTestFailed': '测试失败',
|
||||
'settings.embeddings.testFailed': '失败:{error}',
|
||||
'settings.embeddings.saving': '保存中…',
|
||||
'settings.embeddings.saved': '已保存。',
|
||||
|
||||
@@ -21,11 +21,10 @@ Each sub-check is **best-effort**: a missing tool or transient failure is
|
||||
reported inline in the Markdown, not fatal. A full lane going red never stops
|
||||
the rest of the report from being produced.
|
||||
|
||||
## Schedule + manual trigger
|
||||
## Scheduling
|
||||
|
||||
- Cron: every Monday at **06:00 UTC** (`0 6 * * 1`).
|
||||
- Manual: **Actions → Weekly Code Review → Run workflow**.
|
||||
- Concurrency: one run at a time; subsequent triggers queue rather than cancel.
|
||||
No scheduled GitHub Actions workflow is currently checked in for this report.
|
||||
Run the script locally when a weekly code-health snapshot is needed.
|
||||
|
||||
## Outputs
|
||||
|
||||
@@ -59,7 +58,7 @@ for the JSON shaping. Missing tools are skipped with a note in the report.
|
||||
- **TODO backlog** — the counter is a direction signal, not an action item
|
||||
on its own. Watch for a rising trend over successive weeks.
|
||||
|
||||
## Disabling / overrides
|
||||
## Retiring
|
||||
|
||||
- **One-off skip** — cancel the scheduled run from the Actions tab.
|
||||
- **Pause indefinitely** — no scheduled workflow is currently installed.
|
||||
|
||||
Regular → Executable
@@ -1,5 +1,5 @@
|
||||
static TEST_ENV_LOCK: std::sync::Mutex<()> = std::sync::Mutex::new(());
|
||||
|
||||
pub(super) fn test_env_lock() -> std::sync::MutexGuard<'static, ()> {
|
||||
TEST_ENV_LOCK.lock().unwrap_or_else(|p| p.into_inner())
|
||||
crate::openhuman::config::TEST_ENV_LOCK
|
||||
.lock()
|
||||
.unwrap_or_else(|p| p.into_inner())
|
||||
}
|
||||
|
||||
@@ -541,12 +541,9 @@ mod tests {
|
||||
#[tokio::test]
|
||||
async fn execute_btc_quote_builds_psbt_signs_and_broadcasts() {
|
||||
let _guard = TEST_LOCK.lock();
|
||||
let _env_guard = crate::openhuman::config::TEST_ENV_LOCK
|
||||
.lock()
|
||||
.unwrap_or_else(|e| e.into_inner());
|
||||
reset_quote_store_for_tests();
|
||||
let temp = TempDir::new().unwrap();
|
||||
setup_wallet_in(&temp).await.unwrap();
|
||||
let _workspace_guard = setup_wallet_in(&temp).await.unwrap();
|
||||
|
||||
// Mock state: collect raw tx hex posted to /tx.
|
||||
let raw_txs: Arc<parking_lot::Mutex<Vec<String>>> =
|
||||
@@ -630,12 +627,9 @@ mod tests {
|
||||
#[tokio::test]
|
||||
async fn execute_btc_quote_rejects_insufficient_utxos() {
|
||||
let _guard = TEST_LOCK.lock();
|
||||
let _env_guard = crate::openhuman::config::TEST_ENV_LOCK
|
||||
.lock()
|
||||
.unwrap_or_else(|e| e.into_inner());
|
||||
reset_quote_store_for_tests();
|
||||
let temp = TempDir::new().unwrap();
|
||||
setup_wallet_in(&temp).await.unwrap();
|
||||
let _workspace_guard = setup_wallet_in(&temp).await.unwrap();
|
||||
|
||||
// Empty UTXO set — must error.
|
||||
let app = Router::new().route(
|
||||
|
||||
@@ -511,11 +511,8 @@ mod tests {
|
||||
#[tokio::test]
|
||||
async fn sign_and_broadcast_evm_signs_raw_calldata() {
|
||||
let _guard = TEST_LOCK.lock();
|
||||
let _env = crate::openhuman::config::TEST_ENV_LOCK
|
||||
.lock()
|
||||
.unwrap_or_else(|e| e.into_inner());
|
||||
let temp = TempDir::new().unwrap();
|
||||
setup_wallet_in(&temp).await.unwrap();
|
||||
let _workspace_guard = setup_wallet_in(&temp).await.unwrap();
|
||||
let (addr, calls) = start_evm_mock(JsonValue::Null, JsonValue::Null).await;
|
||||
set_evm_rpc(addr);
|
||||
let result = sign_and_broadcast_evm(
|
||||
|
||||
@@ -808,12 +808,9 @@ mod tests {
|
||||
#[tokio::test]
|
||||
async fn execute_solana_quote_signs_and_broadcasts_native_transfer() {
|
||||
let _guard = TEST_LOCK.lock();
|
||||
let _env_guard = crate::openhuman::config::TEST_ENV_LOCK
|
||||
.lock()
|
||||
.unwrap_or_else(|e| e.into_inner());
|
||||
reset_quote_store_for_tests();
|
||||
let temp = TempDir::new().unwrap();
|
||||
setup_wallet_in(&temp).await.unwrap();
|
||||
let _workspace_guard = setup_wallet_in(&temp).await.unwrap();
|
||||
|
||||
let fake_sig = "5xS9pXmqVz8R1nuRZTfsdsAxBdBFmtnAtuYbCsmK5DYzGn5vR4VqWGmiR5McLnYx8oFqLdo62q4qiUZpQyR4Hkn3";
|
||||
let (addr, calls) = start_solana_mock(fake_sig).await;
|
||||
@@ -864,12 +861,9 @@ mod tests {
|
||||
#[tokio::test]
|
||||
async fn execute_solana_quote_signs_and_broadcasts_spl_transfer() {
|
||||
let _guard = TEST_LOCK.lock();
|
||||
let _env_guard = crate::openhuman::config::TEST_ENV_LOCK
|
||||
.lock()
|
||||
.unwrap_or_else(|e| e.into_inner());
|
||||
reset_quote_store_for_tests();
|
||||
let temp = TempDir::new().unwrap();
|
||||
setup_wallet_in(&temp).await.unwrap();
|
||||
let _workspace_guard = setup_wallet_in(&temp).await.unwrap();
|
||||
|
||||
let fake_sig = "5xS9pXmqVz8R1nuRZTfsdsAxBdBFmtnAtuYbCsmK5DYzGn5vR4VqWGmiR5McLnYx8oFqLdo62q4qiUZpQyR4Hkn3";
|
||||
let (addr, calls) = start_solana_mock(fake_sig).await;
|
||||
@@ -937,12 +931,9 @@ mod tests {
|
||||
#[tokio::test]
|
||||
async fn execute_solana_quote_refuses_spl_when_destination_ata_missing() {
|
||||
let _guard = TEST_LOCK.lock();
|
||||
let _env_guard = crate::openhuman::config::TEST_ENV_LOCK
|
||||
.lock()
|
||||
.unwrap_or_else(|e| e.into_inner());
|
||||
reset_quote_store_for_tests();
|
||||
let temp = TempDir::new().unwrap();
|
||||
setup_wallet_in(&temp).await.unwrap();
|
||||
let _workspace_guard = setup_wallet_in(&temp).await.unwrap();
|
||||
|
||||
// Custom mock that returns null for getAccountInfo — simulates an ATA
|
||||
// that was never created on-chain.
|
||||
@@ -1069,12 +1060,9 @@ mod tests {
|
||||
#[tokio::test]
|
||||
async fn sign_and_broadcast_versioned_fills_signature_and_broadcasts() {
|
||||
let _guard = TEST_LOCK.lock();
|
||||
let _env_guard = crate::openhuman::config::TEST_ENV_LOCK
|
||||
.lock()
|
||||
.unwrap_or_else(|e| e.into_inner());
|
||||
reset_quote_store_for_tests();
|
||||
let temp = TempDir::new().unwrap();
|
||||
setup_wallet_in(&temp).await.unwrap();
|
||||
let _workspace_guard = setup_wallet_in(&temp).await.unwrap();
|
||||
|
||||
let fake_sig = "5xS9pXmqVz8R1nuRZTfsdsAxBdBFmtnAtuYbCsmK5DYzGn5vR4VqWGmiR5McLnYx8oFqLdo62q4qiUZpQyR4Hkn3";
|
||||
let (addr, calls) = start_solana_mock(fake_sig).await;
|
||||
@@ -1108,12 +1096,9 @@ mod tests {
|
||||
#[tokio::test]
|
||||
async fn sign_and_broadcast_versioned_rejects_non_signer() {
|
||||
let _guard = TEST_LOCK.lock();
|
||||
let _env_guard = crate::openhuman::config::TEST_ENV_LOCK
|
||||
.lock()
|
||||
.unwrap_or_else(|e| e.into_inner());
|
||||
reset_quote_store_for_tests();
|
||||
let temp = TempDir::new().unwrap();
|
||||
setup_wallet_in(&temp).await.unwrap();
|
||||
let _workspace_guard = setup_wallet_in(&temp).await.unwrap();
|
||||
|
||||
// A signer pubkey that is NOT our wallet — sign must refuse.
|
||||
let other = [7u8; 32];
|
||||
|
||||
@@ -542,12 +542,9 @@ mod tests {
|
||||
#[tokio::test]
|
||||
async fn execute_tron_quote_signs_and_broadcasts_native_transfer() {
|
||||
let _guard = TEST_LOCK.lock();
|
||||
let _env_guard = crate::openhuman::config::TEST_ENV_LOCK
|
||||
.lock()
|
||||
.unwrap_or_else(|e| e.into_inner());
|
||||
reset_quote_store_for_tests();
|
||||
let temp = TempDir::new().unwrap();
|
||||
setup_wallet_in(&temp).await.unwrap();
|
||||
let _workspace_guard = setup_wallet_in(&temp).await.unwrap();
|
||||
|
||||
let record = TronMockRecord::default();
|
||||
let addr = start_tron_mock(record.clone()).await;
|
||||
@@ -597,12 +594,9 @@ mod tests {
|
||||
#[tokio::test]
|
||||
async fn execute_tron_quote_signs_and_broadcasts_trc20_transfer() {
|
||||
let _guard = TEST_LOCK.lock();
|
||||
let _env_guard = crate::openhuman::config::TEST_ENV_LOCK
|
||||
.lock()
|
||||
.unwrap_or_else(|e| e.into_inner());
|
||||
reset_quote_store_for_tests();
|
||||
let temp = TempDir::new().unwrap();
|
||||
setup_wallet_in(&temp).await.unwrap();
|
||||
let _workspace_guard = setup_wallet_in(&temp).await.unwrap();
|
||||
|
||||
let record = TronMockRecord::default();
|
||||
let addr = start_tron_mock(record.clone()).await;
|
||||
@@ -651,12 +645,9 @@ mod tests {
|
||||
#[tokio::test]
|
||||
async fn execute_tron_quote_surfaces_node_rejection() {
|
||||
let _guard = TEST_LOCK.lock();
|
||||
let _env_guard = crate::openhuman::config::TEST_ENV_LOCK
|
||||
.lock()
|
||||
.unwrap_or_else(|e| e.into_inner());
|
||||
reset_quote_store_for_tests();
|
||||
let temp = TempDir::new().unwrap();
|
||||
setup_wallet_in(&temp).await.unwrap();
|
||||
let _workspace_guard = setup_wallet_in(&temp).await.unwrap();
|
||||
|
||||
// Custom mock returning result=false on broadcast.
|
||||
let app = Router::new()
|
||||
|
||||
@@ -182,12 +182,9 @@ async fn supported_assets_lists_default_erc20s_and_l2() {
|
||||
#[tokio::test]
|
||||
async fn prepare_transfer_rejects_unknown_asset_symbol() {
|
||||
let _guard = TEST_LOCK.lock();
|
||||
let _env_guard = crate::openhuman::config::TEST_ENV_LOCK
|
||||
.lock()
|
||||
.unwrap_or_else(|e| e.into_inner());
|
||||
reset_quote_store_for_tests();
|
||||
let temp = TempDir::new().unwrap();
|
||||
setup_wallet_in(&temp).await.unwrap();
|
||||
let _workspace_guard = setup_wallet_in(&temp).await.unwrap();
|
||||
let err = prepare_transfer(PrepareTransferParams {
|
||||
chain: WalletChain::Evm,
|
||||
to_address: "0x1111111111111111111111111111111111111111".into(),
|
||||
@@ -203,11 +200,8 @@ async fn prepare_transfer_rejects_unknown_asset_symbol() {
|
||||
#[tokio::test]
|
||||
async fn balances_fans_evm_account_into_eth_base_bsc_rows() {
|
||||
let _guard = TEST_LOCK.lock();
|
||||
let _env_guard = crate::openhuman::config::TEST_ENV_LOCK
|
||||
.lock()
|
||||
.unwrap_or_else(|e| e.into_inner());
|
||||
let temp = TempDir::new().unwrap();
|
||||
setup_wallet_in(&temp).await.unwrap();
|
||||
let _workspace_guard = setup_wallet_in(&temp).await.unwrap();
|
||||
// Point all three displayed EVM networks at a mock returning 1e18 wei.
|
||||
let (addr, _estimate_calls, _raw_txs) = start_mock_rpc().await.unwrap();
|
||||
for var in [
|
||||
@@ -266,12 +260,9 @@ async fn tx_status_rejects_empty_hash() {
|
||||
#[tokio::test]
|
||||
async fn execute_prepared_broadcasts_native_evm_transaction() {
|
||||
let _guard = TEST_LOCK.lock();
|
||||
let _env_guard = crate::openhuman::config::TEST_ENV_LOCK
|
||||
.lock()
|
||||
.unwrap_or_else(|e| e.into_inner());
|
||||
reset_quote_store_for_tests();
|
||||
let temp = TempDir::new().unwrap();
|
||||
setup_wallet_in(&temp).await.unwrap();
|
||||
let _workspace_guard = setup_wallet_in(&temp).await.unwrap();
|
||||
let (addr, estimate_calls, raw_txs) = start_mock_rpc().await.unwrap();
|
||||
std::env::set_var("OPENHUMAN_WALLET_RPC_EVM", format!("http://{addr}"));
|
||||
|
||||
@@ -306,12 +297,9 @@ async fn execute_prepared_broadcasts_native_evm_transaction() {
|
||||
#[tokio::test]
|
||||
async fn execute_prepared_broadcasts_erc20_transfer_using_default_token_catalog() {
|
||||
let _guard = TEST_LOCK.lock();
|
||||
let _env_guard = crate::openhuman::config::TEST_ENV_LOCK
|
||||
.lock()
|
||||
.unwrap_or_else(|e| e.into_inner());
|
||||
reset_quote_store_for_tests();
|
||||
let temp = TempDir::new().unwrap();
|
||||
setup_wallet_in(&temp).await.unwrap();
|
||||
let _workspace_guard = setup_wallet_in(&temp).await.unwrap();
|
||||
let (addr, estimate_calls, raw_txs) = start_mock_rpc().await.unwrap();
|
||||
std::env::set_var("OPENHUMAN_WALLET_RPC_EVM", format!("http://{addr}"));
|
||||
|
||||
@@ -350,12 +338,9 @@ async fn execute_prepared_broadcasts_erc20_transfer_using_default_token_catalog(
|
||||
#[tokio::test]
|
||||
async fn execute_prepared_broadcasts_native_evm_on_base_with_chain_id_8453() {
|
||||
let _guard = TEST_LOCK.lock();
|
||||
let _env_guard = crate::openhuman::config::TEST_ENV_LOCK
|
||||
.lock()
|
||||
.unwrap_or_else(|e| e.into_inner());
|
||||
reset_quote_store_for_tests();
|
||||
let temp = TempDir::new().unwrap();
|
||||
setup_wallet_in(&temp).await.unwrap();
|
||||
let _workspace_guard = setup_wallet_in(&temp).await.unwrap();
|
||||
// Base uses chain_id 8453 = 0x2105.
|
||||
let (addr, _estimate_calls, raw_txs) = start_mock_rpc_with_chain_id("0x2105").await.unwrap();
|
||||
std::env::set_var("OPENHUMAN_WALLET_RPC_BASE", format!("http://{addr}"));
|
||||
@@ -585,12 +570,9 @@ async fn execute_prepared_owner_mismatch_error_matches_not_found_shape() {
|
||||
async fn prepare_stamps_owner_via_task_local() {
|
||||
use crate::openhuman::approval::APPROVAL_CHAT_CONTEXT;
|
||||
let _guard = TEST_LOCK.lock();
|
||||
let _env_guard = crate::openhuman::config::TEST_ENV_LOCK
|
||||
.lock()
|
||||
.unwrap_or_else(|e| e.into_inner());
|
||||
reset_quote_store_for_tests();
|
||||
let temp = TempDir::new().unwrap();
|
||||
setup_wallet_in(&temp).await.unwrap();
|
||||
let _workspace_guard = setup_wallet_in(&temp).await.unwrap();
|
||||
|
||||
let expected = owner_a();
|
||||
let ctx = chat_ctx_from(&expected);
|
||||
@@ -620,12 +602,9 @@ async fn prepare_stamps_owner_via_task_local() {
|
||||
#[tokio::test]
|
||||
async fn execute_prepared_rejects_evm_chain_id_mismatch() {
|
||||
let _guard = TEST_LOCK.lock();
|
||||
let _env_guard = crate::openhuman::config::TEST_ENV_LOCK
|
||||
.lock()
|
||||
.unwrap_or_else(|e| e.into_inner());
|
||||
reset_quote_store_for_tests();
|
||||
let temp = TempDir::new().unwrap();
|
||||
setup_wallet_in(&temp).await.unwrap();
|
||||
let _workspace_guard = setup_wallet_in(&temp).await.unwrap();
|
||||
// Quote says Base; mock reports Ethereum (0x1) — must fail.
|
||||
let (addr, _e, _r) = start_mock_rpc_with_chain_id("0x1").await.unwrap();
|
||||
std::env::set_var("OPENHUMAN_WALLET_RPC_BASE", format!("http://{addr}"));
|
||||
|
||||
@@ -995,11 +995,8 @@ mod tests {
|
||||
async fn reveal_recovery_phrase_returns_error_when_no_wallet() {
|
||||
let temp = tempfile::tempdir().expect("temp dir");
|
||||
let _wallet_lock = crate::openhuman::wallet::test_support::TEST_LOCK.lock();
|
||||
let _env_lock = crate::openhuman::config::TEST_ENV_LOCK
|
||||
.lock()
|
||||
.unwrap_or_else(|e| e.into_inner());
|
||||
let _workspace =
|
||||
crate::openhuman::wallet::test_support::WorkspaceEnvGuard::set(temp.path());
|
||||
let _workspace_guard =
|
||||
crate::openhuman::wallet::test_support::set_workspace_env_for_test(&temp);
|
||||
let result = reveal_recovery_phrase().await;
|
||||
let err = result.expect_err("should error when no wallet configured");
|
||||
assert!(
|
||||
@@ -1012,12 +1009,7 @@ mod tests {
|
||||
async fn reveal_recovery_phrase_returns_phrase_for_existing_wallet() {
|
||||
let temp = tempfile::tempdir().expect("temp dir");
|
||||
let _wallet_lock = crate::openhuman::wallet::test_support::TEST_LOCK.lock();
|
||||
let _env_lock = crate::openhuman::config::TEST_ENV_LOCK
|
||||
.lock()
|
||||
.unwrap_or_else(|e| e.into_inner());
|
||||
let _workspace =
|
||||
crate::openhuman::wallet::test_support::WorkspaceEnvGuard::set(temp.path());
|
||||
crate::openhuman::wallet::test_support::setup_wallet_in(&temp)
|
||||
let _workspace_guard = crate::openhuman::wallet::test_support::setup_wallet_in(&temp)
|
||||
.await
|
||||
.expect("setup wallet");
|
||||
let result = reveal_recovery_phrase()
|
||||
|
||||
@@ -17,6 +17,7 @@ use tempfile::TempDir;
|
||||
|
||||
use super::ops::{setup, WalletAccount, WalletChain, WalletSetupParams, WalletSetupSource};
|
||||
use crate::openhuman::config::rpc as config_rpc;
|
||||
use crate::openhuman::config::TEST_ENV_LOCK;
|
||||
|
||||
pub(crate) static TEST_LOCK: Lazy<Mutex<()>> = Lazy::new(|| Mutex::new(()));
|
||||
|
||||
@@ -61,13 +62,20 @@ pub(crate) fn sample_account(chain: WalletChain) -> WalletAccount {
|
||||
/// out of scope at the end of the test.
|
||||
pub(crate) struct WorkspaceEnvGuard {
|
||||
prev: Option<std::ffi::OsString>,
|
||||
_env_lock: std::sync::MutexGuard<'static, ()>,
|
||||
}
|
||||
|
||||
impl WorkspaceEnvGuard {
|
||||
pub(crate) fn set(path: impl AsRef<Path>) -> Self {
|
||||
// OPENHUMAN_WORKSPACE is process-global, so hold the shared config env
|
||||
// lock for the full lifetime of the test workspace override.
|
||||
let env_lock = TEST_ENV_LOCK.lock().unwrap_or_else(|e| e.into_inner());
|
||||
let prev = std::env::var_os("OPENHUMAN_WORKSPACE");
|
||||
std::env::set_var("OPENHUMAN_WORKSPACE", path.as_ref());
|
||||
Self { prev }
|
||||
Self {
|
||||
prev,
|
||||
_env_lock: env_lock,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -80,11 +88,15 @@ impl Drop for WorkspaceEnvGuard {
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) async fn setup_wallet_in(temp: &TempDir) -> Result<(), String> {
|
||||
// We intentionally leak the env-var change for the duration of the test
|
||||
// (wallet state lookups rely on it). Callers that run under the full lib
|
||||
// suite hold the repo-wide config TEST_ENV_LOCK while this env var is set.
|
||||
std::env::set_var("OPENHUMAN_WORKSPACE", temp.path());
|
||||
pub(crate) fn set_workspace_env_for_test(temp: &TempDir) -> WorkspaceEnvGuard {
|
||||
WorkspaceEnvGuard::set(temp.path())
|
||||
}
|
||||
|
||||
pub(crate) async fn setup_wallet_in(temp: &TempDir) -> Result<WorkspaceEnvGuard, String> {
|
||||
// Wallet state lookups rely on OPENHUMAN_WORKSPACE for the duration of
|
||||
// each test. Return a guard so the tempdir path does not leak into later
|
||||
// parallel tests after this test's TempDir has been dropped.
|
||||
let workspace_guard = set_workspace_env_for_test(temp);
|
||||
let config = config_rpc::load_config_with_timeout().await?;
|
||||
let encrypted = crate::openhuman::encryption::rpc::encrypt_secret(&config, TEST_MNEMONIC)
|
||||
.await?
|
||||
@@ -107,5 +119,43 @@ pub(crate) async fn setup_wallet_in(temp: &TempDir) -> Result<(), String> {
|
||||
force: true,
|
||||
})
|
||||
.await?;
|
||||
Ok(())
|
||||
Ok(workspace_guard)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[tokio::test]
|
||||
async fn workspace_env_guard_restores_workspace_env_when_dropped() {
|
||||
let env_lock = TEST_ENV_LOCK.lock().unwrap_or_else(|e| e.into_inner());
|
||||
let previous = std::env::var_os("OPENHUMAN_WORKSPACE");
|
||||
std::env::set_var("OPENHUMAN_WORKSPACE", "/tmp/openhuman-existing-workspace");
|
||||
|
||||
let temp = TempDir::new().expect("temp dir");
|
||||
let prev = std::env::var_os("OPENHUMAN_WORKSPACE");
|
||||
std::env::set_var("OPENHUMAN_WORKSPACE", temp.path());
|
||||
let workspace_guard = WorkspaceEnvGuard {
|
||||
prev,
|
||||
_env_lock: env_lock,
|
||||
};
|
||||
assert_eq!(
|
||||
std::env::var_os("OPENHUMAN_WORKSPACE"),
|
||||
Some(temp.path().as_os_str().to_os_string())
|
||||
);
|
||||
|
||||
drop(workspace_guard);
|
||||
let _cleanup_lock = TEST_ENV_LOCK.lock().unwrap_or_else(|e| e.into_inner());
|
||||
assert_eq!(
|
||||
std::env::var_os("OPENHUMAN_WORKSPACE"),
|
||||
Some(std::ffi::OsString::from(
|
||||
"/tmp/openhuman-existing-workspace"
|
||||
))
|
||||
);
|
||||
|
||||
match previous {
|
||||
Some(value) => std::env::set_var("OPENHUMAN_WORKSPACE", value),
|
||||
None => std::env::remove_var("OPENHUMAN_WORKSPACE"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user