fix(ci): restore green CI — libsqlcipher-dev, prep/survey test drift
Backend: add apt-get install libsqlcipher-dev before pip install so pysqlcipher3 builds in the runner image. Frontend: prep.test.ts was missing a qa mock (fetchFor now calls 5 endpoints in parallel; tests only mocked 4 — 5th returned undefined, threw in catch, research.value never set). survey.test.ts: analyze() was refactored from sync-result to async-task+poll; update test to mock POST then poll completion. Also remove Classic UI (Streamlit) button from AppNav — Streamlit is deprecated and the button caused an unrecoverable redirect loop.
This commit is contained in:
parent
0d6ddd35cf
commit
b44a7975bc
4 changed files with 19 additions and 45 deletions
|
|
@ -23,6 +23,9 @@ jobs:
|
|||
python-version: '3.12'
|
||||
cache: pip
|
||||
|
||||
- name: Install system dependencies
|
||||
run: sudo apt-get update -q && sudo apt-get install -y libsqlcipher-dev
|
||||
|
||||
- name: Install dependencies
|
||||
run: pip install -r requirements.txt
|
||||
|
||||
|
|
|
|||
|
|
@ -59,9 +59,6 @@
|
|||
<Cog6ToothIcon class="sidebar__icon" aria-hidden="true" />
|
||||
<span class="sidebar__label">Settings</span>
|
||||
</RouterLink>
|
||||
<button class="sidebar__classic-btn" @click="switchToClassic" title="Switch to Classic (Streamlit) UI">
|
||||
⚡ Classic
|
||||
</button>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
|
|
@ -134,23 +131,6 @@ function exitHackerMode() {
|
|||
restoreTheme()
|
||||
}
|
||||
|
||||
const _apiBase = import.meta.env.BASE_URL.replace(/\/$/, '')
|
||||
|
||||
async function switchToClassic() {
|
||||
// Persist preference via API so Streamlit reads streamlit from user.yaml
|
||||
// and won't re-set the cookie back to vue (avoids the ?prgn_switch rerun cycle)
|
||||
try {
|
||||
await fetch(_apiBase + '/api/settings/ui-preference', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ preference: 'streamlit' }),
|
||||
})
|
||||
} catch { /* non-fatal — cookie below is enough for immediate redirect */ }
|
||||
document.cookie = 'prgn_ui=streamlit; path=/; SameSite=Lax'
|
||||
// Navigate to root (no query params) — Caddy routes to Streamlit based on cookie
|
||||
window.location.href = window.location.origin + '/'
|
||||
}
|
||||
|
||||
const navLinks = computed(() => [
|
||||
{ to: '/', icon: HomeIcon, label: 'Home' },
|
||||
{ to: '/review', icon: ClipboardDocumentListIcon, label: 'Job Review' },
|
||||
|
|
@ -321,29 +301,6 @@ const mobileLinks = [
|
|||
margin: 0;
|
||||
}
|
||||
|
||||
.sidebar__classic-btn {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
width: 100%;
|
||||
padding: var(--space-2) var(--space-3);
|
||||
margin-top: var(--space-1);
|
||||
background: none;
|
||||
border: none;
|
||||
border-radius: var(--radius-md);
|
||||
color: var(--color-text-muted);
|
||||
font-size: var(--text-xs);
|
||||
font-weight: 500;
|
||||
cursor: pointer;
|
||||
opacity: 0.6;
|
||||
transition: opacity 150ms, background 150ms;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.sidebar__classic-btn:hover {
|
||||
opacity: 1;
|
||||
background: var(--color-surface-alt);
|
||||
}
|
||||
|
||||
/* ── Theme picker ───────────────────────────────────── */
|
||||
.sidebar__theme {
|
||||
padding: var(--space-2) var(--space-3);
|
||||
|
|
|
|||
|
|
@ -27,6 +27,7 @@ describe('usePrepStore', () => {
|
|||
tech_brief: null, funding_brief: null, red_flags: null, accessibility_brief: null,
|
||||
generated_at: '2026-03-20T12:00:00' }, error: null }) // research
|
||||
.mockResolvedValueOnce({ data: [], error: null }) // contacts
|
||||
.mockResolvedValueOnce({ data: [], error: null }) // qa
|
||||
.mockResolvedValueOnce({ data: { status: 'none', stage: null, message: null }, error: null }) // task
|
||||
.mockResolvedValueOnce({ data: { id: 1, title: 'Engineer', company: 'Acme', url: null,
|
||||
description: 'Build things.', cover_letter: null, match_score: 80,
|
||||
|
|
@ -50,6 +51,7 @@ describe('usePrepStore', () => {
|
|||
tech_brief: null, funding_brief: null, red_flags: null, accessibility_brief: null,
|
||||
generated_at: null }, error: null })
|
||||
.mockResolvedValueOnce({ data: [], error: null })
|
||||
.mockResolvedValueOnce({ data: [], error: null }) // qa
|
||||
.mockResolvedValueOnce({ data: { status: 'none', stage: null, message: null }, error: null })
|
||||
.mockResolvedValueOnce({ data: { id: 1, title: 'Old Job', company: 'OldCo', url: null,
|
||||
description: null, cover_letter: null, match_score: null, keyword_gaps: null }, error: null })
|
||||
|
|
@ -62,6 +64,7 @@ describe('usePrepStore', () => {
|
|||
mockApiFetch
|
||||
.mockResolvedValueOnce({ data: null, error: { kind: 'http', status: 404, detail: '' } }) // 404 → null
|
||||
.mockResolvedValueOnce({ data: [], error: null })
|
||||
.mockResolvedValueOnce({ data: [], error: null }) // qa
|
||||
.mockResolvedValueOnce({ data: { status: 'none', stage: null, message: null }, error: null })
|
||||
.mockResolvedValueOnce({ data: { id: 2, title: 'New Job', company: 'NewCo', url: null,
|
||||
description: null, cover_letter: null, match_score: null, keyword_gaps: null }, error: null })
|
||||
|
|
@ -102,6 +105,7 @@ describe('usePrepStore', () => {
|
|||
tech_brief: null, funding_brief: null, red_flags: null, accessibility_brief: null,
|
||||
generated_at: null }, error: null })
|
||||
.mockResolvedValueOnce({ data: [], error: null })
|
||||
.mockResolvedValueOnce({ data: [], error: null }) // qa
|
||||
.mockResolvedValueOnce({ data: { status: 'none', stage: null, message: null }, error: null })
|
||||
.mockResolvedValueOnce({ data: { id: 1, title: 'Eng', company: 'Acme', url: null,
|
||||
description: null, cover_letter: null, match_score: null, keyword_gaps: null }, error: null })
|
||||
|
|
@ -112,11 +116,12 @@ describe('usePrepStore', () => {
|
|||
// Mock first poll → completed
|
||||
mockApiFetch
|
||||
.mockResolvedValueOnce({ data: { status: 'completed', stage: null, message: null }, error: null })
|
||||
// re-fetch on completed: research, contacts, task, fullJob
|
||||
// re-fetch on completed: research, contacts, qa, task, fullJob
|
||||
.mockResolvedValueOnce({ data: { company_brief: 'Updated!', ceo_brief: null, talking_points: null,
|
||||
tech_brief: null, funding_brief: null, red_flags: null, accessibility_brief: null,
|
||||
generated_at: '2026-03-20T13:00:00' }, error: null })
|
||||
.mockResolvedValueOnce({ data: [], error: null })
|
||||
.mockResolvedValueOnce({ data: [], error: null }) // qa
|
||||
.mockResolvedValueOnce({ data: { status: 'completed', stage: null, message: null }, error: null })
|
||||
.mockResolvedValueOnce({ data: { id: 1, title: 'Eng', company: 'Acme', url: null,
|
||||
description: 'Now with content', cover_letter: null, match_score: null, keyword_gaps: null }, error: null })
|
||||
|
|
@ -134,6 +139,7 @@ describe('usePrepStore', () => {
|
|||
tech_brief: null, funding_brief: null, red_flags: null, accessibility_brief: null,
|
||||
generated_at: null }, error: null })
|
||||
.mockResolvedValueOnce({ data: [], error: null })
|
||||
.mockResolvedValueOnce({ data: [], error: null }) // qa
|
||||
.mockResolvedValueOnce({ data: { status: 'none', stage: null, message: null }, error: null })
|
||||
.mockResolvedValueOnce({ data: { id: 1, title: 'Eng', company: 'Acme', url: null,
|
||||
description: null, cover_letter: null, match_score: null, keyword_gaps: null }, error: null })
|
||||
|
|
@ -162,6 +168,7 @@ describe('usePrepStore', () => {
|
|||
tech_brief: null, funding_brief: null, red_flags: null, accessibility_brief: null,
|
||||
generated_at: '2026-03-20T12:00:00' }, error: null }) // research OK
|
||||
.mockResolvedValueOnce({ data: null, error: { kind: 'http', status: 500, detail: 'DB error' } }) // contacts fail
|
||||
.mockResolvedValueOnce({ data: [], error: null }) // qa
|
||||
.mockResolvedValueOnce({ data: { status: 'none', stage: null, message: null }, error: null }) // task OK
|
||||
.mockResolvedValueOnce({ data: { id: 1, title: 'Engineer', company: 'Acme', url: null,
|
||||
description: 'Build things.', cover_letter: null, match_score: 80,
|
||||
|
|
|
|||
|
|
@ -54,14 +54,20 @@ describe('useSurveyStore', () => {
|
|||
})
|
||||
|
||||
it('analyze stores result including mode and rawInput', async () => {
|
||||
vi.useFakeTimers()
|
||||
const mockApiFetch = vi.mocked(useApiFetch)
|
||||
// POST → task accepted
|
||||
mockApiFetch.mockResolvedValueOnce({ data: { task_id: 7, is_new: true }, error: null })
|
||||
// Poll → completed with result
|
||||
mockApiFetch.mockResolvedValueOnce({
|
||||
data: { output: '1. B — reason', source: 'text_paste' },
|
||||
data: { status: 'completed', stage: null, message: null,
|
||||
result: { output: '1. B — reason', source: 'text_paste' } },
|
||||
error: null,
|
||||
})
|
||||
|
||||
const store = useSurveyStore()
|
||||
await store.analyze(1, { text: 'Q1: test', mode: 'quick' })
|
||||
await vi.advanceTimersByTimeAsync(3000)
|
||||
|
||||
expect(store.analysis).not.toBeNull()
|
||||
expect(store.analysis!.output).toBe('1. B — reason')
|
||||
|
|
@ -69,6 +75,7 @@ describe('useSurveyStore', () => {
|
|||
expect(store.analysis!.mode).toBe('quick')
|
||||
expect(store.analysis!.rawInput).toBe('Q1: test')
|
||||
expect(store.loading).toBe(false)
|
||||
vi.useRealTimers()
|
||||
})
|
||||
|
||||
it('analyze sets error on failure', async () => {
|
||||
|
|
|
|||
Loading…
Reference in a new issue