fix(settings): prompt for managed embeddings sign-in (#3858)

Co-authored-by: Steven Enamakel <enamakel@tinyhumans.ai>
This commit is contained in:
YOMXXX
2026-06-22 15:11:47 -07:00
committed by GitHub
co-authored by Steven Enamakel
parent 9e5d0329bf
commit a15da11c5f
26 changed files with 430 additions and 101 deletions
@@ -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 () => {
+11
View File
@@ -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': 'تم الحفظ.',
+11
View File
@@ -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': 'সংরক্ষিত।',
+11
View File
@@ -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.',
+11
View File
@@ -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.',
+11
View File
@@ -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.',
+11
View File
@@ -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é.',
+11
View File
@@ -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': 'सहेजा गया।',
+11
View File
@@ -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.',
+11
View File
@@ -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 laccesso 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.',
+11
View File
@@ -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': '저장됨.',
+11
View File
@@ -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.',
+11
View File
@@ -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.',
+11
View File
@@ -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': 'Сохранено.',
+11
View File
@@ -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': '已保存。',
+4 -5
View File
@@ -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
View File
+3 -3
View File
@@ -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())
}
+2 -8
View File
@@ -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(
+1 -4
View File
@@ -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(
+5 -20
View File
@@ -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];
+3 -12
View File
@@ -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()
+7 -28
View File
@@ -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}"));
+3 -11
View File
@@ -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()
+57 -7
View File
@@ -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"),
}
}
}