feat(woo): S4 Woo client + taxonomy sync + test-woo + admin taxonomy

- backend/woo.py: stdlib urllib Woo v3 client. Basic-auth, HTTPS-only, X-WP-TotalPages
  pagination, typed errors (CredentialsMissing/Auth/NotFound/Server/Connection),
  injectable transport for tests, WP core media upload.
- backend/taxonomy.py: sync pulls categories/tags/attributes/terms into cache in one
  commit (rollback keeps prior cache on failure); fig-*/feat-* tag scoping; stable
  taxonomy_anchor hash; get_cached grouped admin view.
- backend/app.py: POST /settings/test-woo (fail-closed, resync on success),
  GET /admin/taxonomy, POST /admin/taxonomy/sync (502 on Woo failure).
- frontend: Settings Test Woo button + result display.
- tests: 8 woo/taxonomy tests (auth header, non-https refusal, pagination, missing
  creds, 401, scoped sync, failure-keeps-cache, anchor stability).

Verified: pytest 31 passed; check_requirements + check_coverage exit 0; npm build ok;
live QA test-woo fail-closed + sync 502.
This commit is contained in:
2026-09-19 16:40:37 -07:00
parent 38eebbc8bb
commit d83c9d5c21
5 changed files with 470 additions and 0 deletions
@@ -91,6 +91,18 @@
saving = false;
}
let testResult: { ok: boolean; detail: string } | null = null;
async function testWoo() {
testResult = null;
try {
const res = await fetch('/api/v1/settings/test-woo', { method: 'POST' });
testResult = await res.json();
} catch {
testResult = { ok: false, detail: 'network error' };
}
}
onMount(() => {
void load();
});
@@ -158,6 +170,12 @@
</div>
<button on:click={save} disabled={saving}>Save Changes</button>
<button on:click={testWoo} disabled={saving}>Test Woo</button>
{#if testResult}
<p class="msg" class:ok={testResult.ok} class:err={!testResult.ok}>
{testResult.ok ? 'Connected & synced' : testResult.detail}
</p>
{/if}
{#if message}<p class="msg">{message}</p>{/if}
{/if}
</div>