diff --git a/frontend/test/adminStore.spec.js b/frontend/test/adminStore.spec.js index 2dcbce8..dcfb0c4 100644 --- a/frontend/test/adminStore.spec.js +++ b/frontend/test/adminStore.spec.js @@ -1,23 +1,12 @@ import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest' import { setActivePinia, createPinia } from 'pinia' import { useAdminStore } from '../src/stores/admin.js' +import { stubFetch } from './support/stubFetch.js' // Covers the two helpers the admin store actions route through (DRY Finding C, // #753): _dryRunPost (URL + dry_run body, sourceId→source_id) and _guard // (lastError capture + rethrow). The store had no frontend test before. -function stubFetch(handler) { - globalThis.fetch = vi.fn(async (url, init) => { - const { status, body } = handler(url, init) - return { - ok: status >= 200 && status < 300, - status, - statusText: String(status), - text: async () => (body == null ? '' : JSON.stringify(body)), - } - }) -} - function lastCallBody(calls) { return JSON.parse(calls.at(-1).init.body) } diff --git a/frontend/test/credentials.spec.js b/frontend/test/credentials.spec.js index 5b8d8fb..b49974d 100644 --- a/frontend/test/credentials.spec.js +++ b/frontend/test/credentials.spec.js @@ -1,17 +1,7 @@ import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest' import { setActivePinia, createPinia } from 'pinia' import { useCredentialsStore } from '../src/stores/credentials.js' - -function stubFetch(handler) { - globalThis.fetch = vi.fn(async (url, init) => { - const { status, body } = handler(url, init) - return { - ok: status >= 200 && status < 300, - status, statusText: String(status), - text: async () => (body == null ? '' : JSON.stringify(body)), - } - }) -} +import { stubFetch } from './support/stubFetch.js' describe('credentials store', () => { beforeEach(() => setActivePinia(createPinia())) diff --git a/frontend/test/dbMaintenance.spec.js b/frontend/test/dbMaintenance.spec.js index f6b36e9..55e871e 100644 --- a/frontend/test/dbMaintenance.spec.js +++ b/frontend/test/dbMaintenance.spec.js @@ -1,18 +1,7 @@ import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest' import { setActivePinia, createPinia } from 'pinia' import { useDbMaintenanceStore } from '../src/stores/dbMaintenance.js' - -function stubFetch(handler) { - globalThis.fetch = vi.fn(async (url, init) => { - const { status, body } = handler(url, init) - return { - ok: status >= 200 && status < 300, - status, - statusText: String(status), - text: async () => (body == null ? '' : JSON.stringify(body)), - } - }) -} +import { stubFetch } from './support/stubFetch.js' describe('dbMaintenance store', () => { beforeEach(() => setActivePinia(createPinia())) diff --git a/frontend/test/downloads.spec.js b/frontend/test/downloads.spec.js index a76fa90..62f870d 100644 --- a/frontend/test/downloads.spec.js +++ b/frontend/test/downloads.spec.js @@ -1,17 +1,7 @@ import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest' import { setActivePinia, createPinia } from 'pinia' import { useDownloadsStore } from '../src/stores/downloads.js' - -function stubFetch(handler) { - globalThis.fetch = vi.fn(async (url, init) => { - const { status, body } = handler(url, init) - return { - ok: status >= 200 && status < 300, - status, statusText: String(status), - text: async () => (body == null ? '' : JSON.stringify(body)), - } - }) -} +import { stubFetch } from './support/stubFetch.js' describe('downloads store', () => { beforeEach(() => setActivePinia(createPinia())) diff --git a/frontend/test/gallery.spec.js b/frontend/test/gallery.spec.js index 1946e8e..3c73bcd 100644 --- a/frontend/test/gallery.spec.js +++ b/frontend/test/gallery.spec.js @@ -1,18 +1,7 @@ import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest' import { setActivePinia, createPinia } from 'pinia' import { cloneFilter, filterToQuery, useGalleryStore } from '../src/stores/gallery.js' - -function stubFetch(handler) { - globalThis.fetch = vi.fn(async (url, init) => { - const { status, body } = handler(url, init) - return { - ok: status >= 200 && status < 300, - status, - statusText: String(status), - text: async () => (body == null ? '' : JSON.stringify(body)), - } - }) -} +import { stubFetch } from './support/stubFetch.js' const EMPTY = { images: [], date_groups: [], next_cursor: null } diff --git a/frontend/test/gallerySelection.spec.js b/frontend/test/gallerySelection.spec.js index 7641692..6139698 100644 --- a/frontend/test/gallerySelection.spec.js +++ b/frontend/test/gallerySelection.spec.js @@ -1,18 +1,7 @@ import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest' import { setActivePinia, createPinia } from 'pinia' import { useGallerySelectionStore } from '../src/stores/gallerySelection.js' - -function stubFetch(handler) { - globalThis.fetch = vi.fn(async (url, init) => { - const { status, body } = handler(url, init) - return { - ok: status >= 200 && status < 300, - status, - statusText: String(status), - text: async () => (body == null ? '' : JSON.stringify(body)) - } - }) -} +import { stubFetch } from './support/stubFetch.js' describe('gallerySelection store', () => { beforeEach(() => setActivePinia(createPinia())) diff --git a/frontend/test/platforms.spec.js b/frontend/test/platforms.spec.js index 6667673..6b77869 100644 --- a/frontend/test/platforms.spec.js +++ b/frontend/test/platforms.spec.js @@ -1,17 +1,7 @@ import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest' import { setActivePinia, createPinia } from 'pinia' import { usePlatformsStore } from '../src/stores/platforms.js' - -function stubFetch(handler) { - globalThis.fetch = vi.fn(async (url, init) => { - const { status, body } = handler(url, init) - return { - ok: status >= 200 && status < 300, - status, statusText: String(status), - text: async () => (body == null ? '' : JSON.stringify(body)), - } - }) -} +import { stubFetch } from './support/stubFetch.js' describe('platforms store', () => { beforeEach(() => setActivePinia(createPinia())) diff --git a/frontend/test/provenance.spec.js b/frontend/test/provenance.spec.js index c5142d6..ad738e1 100644 --- a/frontend/test/provenance.spec.js +++ b/frontend/test/provenance.spec.js @@ -1,18 +1,7 @@ import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest' import { setActivePinia, createPinia } from 'pinia' import { useProvenanceStore } from '../src/stores/provenance.js' - -function stubFetch(handler) { - globalThis.fetch = vi.fn(async (url, init) => { - const { status, body } = handler(url, init) - return { - ok: status >= 200 && status < 300, - status, - statusText: String(status), - text: async () => (body == null ? '' : JSON.stringify(body)) - } - }) -} +import { stubFetch } from './support/stubFetch.js' describe('provenance store', () => { beforeEach(() => setActivePinia(createPinia())) diff --git a/frontend/test/seriesManage.spec.js b/frontend/test/seriesManage.spec.js index 8a19744..a9f05da 100644 --- a/frontend/test/seriesManage.spec.js +++ b/frontend/test/seriesManage.spec.js @@ -1,17 +1,7 @@ import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest' import { setActivePinia, createPinia } from 'pinia' import { useSeriesManageStore, moveItem } from '../src/stores/seriesManage.js' - -function stubFetch(handler) { - globalThis.fetch = vi.fn(async (url, init) => { - const { status, body } = handler(url, init) - return { - ok: status >= 200 && status < 300, - status, statusText: String(status), - text: async () => (body == null ? '' : JSON.stringify(body)) - } - }) -} +import { stubFetch } from './support/stubFetch.js' // FC-6.x: a flat page run + cosmetic chapter dividers. const SERIES_BODY = { diff --git a/frontend/test/seriesReader.spec.js b/frontend/test/seriesReader.spec.js index de4d61e..cce2434 100644 --- a/frontend/test/seriesReader.spec.js +++ b/frontend/test/seriesReader.spec.js @@ -6,17 +6,7 @@ import { progressPct, clampPage } from '../src/stores/seriesReader.js' - -function stubFetch(handler) { - globalThis.fetch = vi.fn(async (url, init) => { - const { status, body } = handler(url, init) - return { - ok: status >= 200 && status < 300, - status, statusText: String(status), - text: async () => (body == null ? '' : JSON.stringify(body)) - } - }) -} +import { stubFetch } from './support/stubFetch.js' const M = [ { page_number: 1, top: 0, height: 100 }, diff --git a/frontend/test/sources.spec.js b/frontend/test/sources.spec.js index 2570c76..ae87f87 100644 --- a/frontend/test/sources.spec.js +++ b/frontend/test/sources.spec.js @@ -1,17 +1,7 @@ import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest' import { setActivePinia, createPinia } from 'pinia' import { useSourcesStore } from '../src/stores/sources.js' - -function stubFetch(handler) { - globalThis.fetch = vi.fn(async (url, init) => { - const { status, body } = handler(url, init) - return { - ok: status >= 200 && status < 300, - status, statusText: String(status), - text: async () => (body == null ? '' : JSON.stringify(body)), - } - }) -} +import { stubFetch } from './support/stubFetch.js' describe('sources store', () => { beforeEach(() => setActivePinia(createPinia())) diff --git a/frontend/test/suggestions.spec.js b/frontend/test/suggestions.spec.js index 69e3dc2..8bdb10f 100644 --- a/frontend/test/suggestions.spec.js +++ b/frontend/test/suggestions.spec.js @@ -1,21 +1,10 @@ import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest' import { setActivePinia, createPinia } from 'pinia' import { useSuggestionsStore } from '../src/stores/suggestions.js' +import { stubFetch } from './support/stubFetch.js' vi.mock('../src/utils/toast.js', () => ({ toast: vi.fn() })) -function stubFetch(handler) { - globalThis.fetch = vi.fn(async (url, init) => { - const { status, body } = handler(url, init) - return { - ok: status >= 200 && status < 300, - status, - statusText: String(status), - text: async () => (body == null ? '' : JSON.stringify(body)), - } - }) -} - // Every suggestion is a canonical DB tag now (tagging-v2): a real id, flagged // above/below its head's suggest threshold. No raw / creates-new / alias cases. const sugg = (over = {}) => ({ diff --git a/frontend/test/support/stubFetch.js b/frontend/test/support/stubFetch.js new file mode 100644 index 0000000..272b56a --- /dev/null +++ b/frontend/test/support/stubFetch.js @@ -0,0 +1,16 @@ +// Replace globalThis.fetch with a stub answering from `handler(url, init)`, +// which returns { status, body }. Fifteen specs carried their own copy of this +// (#3109); a response shape change now has one place to go. +import { vi } from 'vitest' + +export function stubFetch (handler) { + globalThis.fetch = vi.fn(async (url, init) => { + const { status, body } = handler(url, init) + return { + ok: status >= 200 && status < 300, + status, + statusText: String(status), + text: async () => (body == null ? '' : JSON.stringify(body)) + } + }) +} diff --git a/frontend/test/tagDirectory.spec.js b/frontend/test/tagDirectory.spec.js index 8fc44e2..e9487d3 100644 --- a/frontend/test/tagDirectory.spec.js +++ b/frontend/test/tagDirectory.spec.js @@ -1,18 +1,7 @@ import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest' import { setActivePinia, createPinia } from 'pinia' import { useTagDirectoryStore } from '../src/stores/tagDirectory.js' - -function stubFetch(handler) { - globalThis.fetch = vi.fn(async (url, init) => { - const { status, body } = handler(url, init) - return { - ok: status >= 200 && status < 300, - status, - statusText: String(status), - text: async () => (body == null ? '' : JSON.stringify(body)) - } - }) -} +import { stubFetch } from './support/stubFetch.js' describe('tagDirectory store: rename / merge', () => { beforeEach(() => setActivePinia(createPinia())) diff --git a/frontend/test/tags.spec.js b/frontend/test/tags.spec.js index acb7658..1d1d6f3 100644 --- a/frontend/test/tags.spec.js +++ b/frontend/test/tags.spec.js @@ -1,18 +1,7 @@ import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest' import { setActivePinia, createPinia } from 'pinia' import { useTagStore } from '../src/stores/tags.js' - -function stubFetch(handler) { - globalThis.fetch = vi.fn(async (url, init) => { - const { status, body } = handler(url, init) - return { - ok: status >= 200 && status < 300, - status, - statusText: String(status), - text: async () => (body == null ? '' : JSON.stringify(body)), - } - }) -} +import { stubFetch } from './support/stubFetch.js' describe('tags store: setFandom', () => { beforeEach(() => setActivePinia(createPinia())) diff --git a/frontend/test/workerLanes.spec.js b/frontend/test/workerLanes.spec.js index 22981de..2d4c74c 100644 --- a/frontend/test/workerLanes.spec.js +++ b/frontend/test/workerLanes.spec.js @@ -1,6 +1,7 @@ import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest' import { setActivePinia, createPinia } from 'pinia' import { laneStuckFor, useSystemActivityStore } from '../src/stores/systemActivity.js' +import { stubFetch } from './support/stubFetch.js' // Milestone 422 step 4. Covers the store half of the worker-lane dial — the // part that decides what the card can tell the operator. @@ -18,18 +19,6 @@ import { laneStuckFor, useSystemActivityStore } from '../src/stores/systemActivi // as one. A control that silently does nothing is worse than one that // refuses out loud. -function stubFetch(handler) { - globalThis.fetch = vi.fn(async (url, init) => { - const { status, body } = handler(url, init) - return { - ok: status >= 200 && status < 300, - status, - statusText: String(status), - text: async () => (body == null ? '' : JSON.stringify(body)), - } - }) -} - const LANES_BODY = { lanes: [ { diff --git a/tests/factories.py b/tests/factories.py new file mode 100644 index 0000000..35e958d --- /dev/null +++ b/tests/factories.py @@ -0,0 +1,47 @@ +"""Throwaway rows for tests: one copy of the placeholder columns (#3109). + +`ImageRecord` needs a unique path and sha256 plus six NOT NULL columns no test +cares about. Fifteen modules re-typed the same builder, so a new NOT NULL column +meant fifteen separate patches. The tests that need these rows import them from +here, usually under their old local name (`make_image as _img`), so their call +sites did not change. +""" + +from backend.app.models import ImageRecord, Tag, TagKind + + +def image_row(sha: str, emb=None, **overrides) -> ImageRecord: + """An unsaved ImageRecord keyed by `sha`; `emb` is its SigLIP embedding.""" + fields = { + "path": f"/images/{sha}.jpg", "sha256": sha, "size_bytes": 1, + "mime": "image/jpeg", "width": 1, "height": 1, + "origin": "imported_filesystem", "integrity_status": "unknown", + } + if emb is not None: + fields["siglip_embedding"] = emb + fields.update(overrides) + return ImageRecord(**fields) + + +def make_image(db, sha: str, emb=None, **overrides) -> ImageRecord: + """image_row, added and flushed on a sync session.""" + img = image_row(sha, emb, **overrides) + db.add(img) + db.flush() + return img + + +async def make_image_async(db, sha: str, emb=None, **overrides) -> ImageRecord: + """image_row, added and flushed on an async session.""" + img = image_row(sha, emb, **overrides) + db.add(img) + await db.flush() + return img + + +def make_tag(db, name: str, **overrides) -> Tag: + """A general tag, added and flushed on a sync session.""" + tag = Tag(**{"name": name, "kind": TagKind.general, **overrides}) + db.add(tag) + db.flush() + return tag diff --git a/tests/test_api_ccip.py b/tests/test_api_ccip.py index d41167a..af6aad5 100644 --- a/tests/test_api_ccip.py +++ b/tests/test_api_ccip.py @@ -4,6 +4,7 @@ import pytest from backend.app.models import ImageRecord, ImageRegion, TagKind from backend.app.models.tag import image_tag from backend.app.services.tag_service import TagService +from tests.factories import make_image_async as _img pytestmark = pytest.mark.integration @@ -14,16 +15,6 @@ def _ccip(slot: int) -> list[float]: return v -async def _img(db, sha) -> ImageRecord: - img = ImageRecord( - path=f"/images/{sha}.jpg", sha256=sha, size_bytes=1, mime="image/jpeg", - width=1, height=1, origin="imported_filesystem", integrity_status="unknown", - ) - db.add(img) - await db.flush() - return img - - async def _figure(db, image_id, ccip): db.add(ImageRegion( image_record_id=image_id, kind="figure", rx=0.0, ry=0.0, rw=1.0, rh=1.0, diff --git a/tests/test_api_gpu.py b/tests/test_api_gpu.py index e36c490..166f743 100644 --- a/tests/test_api_gpu.py +++ b/tests/test_api_gpu.py @@ -7,20 +7,11 @@ from sqlalchemy import func, select from backend.app.models import GpuJob, ImageRecord from backend.app.services.ml.gpu_jobs import GpuJobService from backend.app.services.ml.regions import RegionService +from tests.factories import make_image_async as _img pytestmark = pytest.mark.integration -async def _img(db, sha) -> ImageRecord: - img = ImageRecord( - path=f"/images/{sha}.jpg", sha256=sha, size_bytes=1, mime="image/jpeg", - width=1, height=1, origin="imported_filesystem", integrity_status="unknown", - ) - db.add(img) - await db.flush() - return img - - @pytest.mark.asyncio async def test_agent_endpoints_require_bearer(client, db): resp = await client.post("/api/gpu/jobs/lease", json={"agent_id": "a1"}) diff --git a/tests/test_api_tag_stats.py b/tests/test_api_tag_stats.py index 67900b6..81004dc 100644 --- a/tests/test_api_tag_stats.py +++ b/tests/test_api_tag_stats.py @@ -5,20 +5,11 @@ from backend.app.models import ImageRecord, TagHead, TagKind from backend.app.models.tag import image_tag from backend.app.models.tag_suggestion_rejection import TagSuggestionRejection from backend.app.services.tag_service import TagService +from tests.factories import make_image_async as _img pytestmark = pytest.mark.integration -async def _img(db, sha) -> ImageRecord: - img = ImageRecord( - path=f"/images/{sha}.jpg", sha256=sha, size_bytes=1, mime="image/jpeg", - width=1, height=1, origin="imported_filesystem", integrity_status="unknown", - ) - db.add(img) - await db.flush() - return img - - async def _apply(db, image_id, tag_id, source): await db.execute(image_tag.insert().values( image_record_id=image_id, tag_id=tag_id, source=source, diff --git a/tests/test_ccip.py b/tests/test_ccip.py index df52378..52d7b40 100644 --- a/tests/test_ccip.py +++ b/tests/test_ccip.py @@ -13,6 +13,7 @@ from backend.app.models import ( from backend.app.models.tag import image_tag from backend.app.services.ml.ccip import match_image from backend.app.services.tag_service import TagService +from tests.factories import make_image_async as _img pytestmark = pytest.mark.integration @@ -23,16 +24,6 @@ def _ccip(slot: int) -> list[float]: return v -async def _img(db, sha) -> ImageRecord: - img = ImageRecord( - path=f"/images/{sha}.jpg", sha256=sha, size_bytes=1, mime="image/jpeg", - width=1, height=1, origin="imported_filesystem", integrity_status="unknown", - ) - db.add(img) - await db.flush() - return img - - async def _figure(db, image_id, ccip): db.add(ImageRegion( image_record_id=image_id, kind="figure", diff --git a/tests/test_character_prototypes.py b/tests/test_character_prototypes.py index be5bd04..3e32763 100644 --- a/tests/test_character_prototypes.py +++ b/tests/test_character_prototypes.py @@ -18,6 +18,7 @@ from backend.app.models.tag import image_tag from backend.app.services.ml.character_prototypes import ( refresh_character_prototypes, ) +from tests.factories import make_image as _img pytestmark = pytest.mark.integration @@ -28,17 +29,6 @@ def _ccip(slot: int = 0) -> list[float]: return v -def _img(db, sha: str) -> ImageRecord: - img = ImageRecord( - path=f"/images/{sha}.jpg", sha256=sha, size_bytes=1, mime="image/jpeg", - width=1, height=1, origin="imported_filesystem", - integrity_status="unknown", - ) - db.add(img) - db.flush() - return img - - def _figure(db, image_id: int, ccip=None) -> None: db.add(ImageRegion( image_record_id=image_id, kind="figure", diff --git a/tests/test_gpu_jobs.py b/tests/test_gpu_jobs.py index 55bcfd6..09f7f41 100644 --- a/tests/test_gpu_jobs.py +++ b/tests/test_gpu_jobs.py @@ -10,20 +10,11 @@ from backend.app.services.ml.gpu_jobs import ( PENDING_POISON_CAP, GpuJobService, ) +from tests.factories import make_image_async as _img pytestmark = pytest.mark.integration -async def _img(db, sha) -> ImageRecord: - img = ImageRecord( - path=f"/images/{sha}.jpg", sha256=sha, size_bytes=1, mime="image/jpeg", - width=1, height=1, origin="imported_filesystem", integrity_status="unknown", - ) - db.add(img) - await db.flush() - return img - - @pytest.mark.asyncio async def test_enqueue_siglip_backfill_gates_on_concept_region(db): # 'siglip' backfill enqueues images that lack a concept region (the diff --git a/tests/test_head_auto_apply.py b/tests/test_head_auto_apply.py index 0d345e0..b9b5b67 100644 --- a/tests/test_head_auto_apply.py +++ b/tests/test_head_auto_apply.py @@ -14,6 +14,7 @@ from backend.app.models import ( ) from backend.app.models.tag import image_tag from backend.app.services.ml.heads import auto_apply_sweep +from tests.factories import make_image as _img pytestmark = pytest.mark.integration @@ -24,17 +25,6 @@ def _emb(slot: int) -> list[float]: return v -def _img(db, sha: str, emb) -> ImageRecord: - img = ImageRecord( - path=f"/images/{sha}.jpg", sha256=sha, size_bytes=1, mime="image/jpeg", - width=1, height=1, origin="imported_filesystem", - integrity_status="unknown", siglip_embedding=emb, - ) - db.add(img) - db.flush() - return img - - def _head(db, tag_id: int, slot: int, *, threshold=0.5, n_pos=60): s = db.execute(select(MLSettings).where(MLSettings.id == 1)).scalar_one() w = [0.0] * 1152 diff --git a/tests/test_head_incremental.py b/tests/test_head_incremental.py index b16b9dd..b1294a5 100644 --- a/tests/test_head_incremental.py +++ b/tests/test_head_incremental.py @@ -17,28 +17,12 @@ from backend.app.services.ml.heads import ( _head_fingerprints, _heads_needing_retrain, ) +from tests.factories import make_image as _img +from tests.factories import make_tag as _tag pytestmark = pytest.mark.integration -def _img(db, sha: str) -> ImageRecord: - img = ImageRecord( - path=f"/images/{sha}.jpg", sha256=sha, size_bytes=1, mime="image/jpeg", - width=1, height=1, origin="imported_filesystem", - integrity_status="unknown", - ) - db.add(img) - db.flush() - return img - - -def _tag(db, name: str) -> Tag: - t = Tag(name=name, kind=TagKind.general) - db.add(t) - db.flush() - return t - - def _apply(db, image_id: int, tag_id: int) -> None: db.execute(image_tag.insert().values( image_record_id=image_id, tag_id=tag_id, source="manual", diff --git a/tests/test_head_metrics.py b/tests/test_head_metrics.py index 189d197..e7956a2 100644 --- a/tests/test_head_metrics.py +++ b/tests/test_head_metrics.py @@ -6,20 +6,11 @@ from sqlalchemy import select from backend.app.models import HeadMetric, HeadMetricsSnapshot, ImageRecord, TagHead, TagKind from backend.app.models.tag import image_tag from backend.app.services.tag_service import TagService +from tests.factories import make_image_async as _img pytestmark = pytest.mark.integration -async def _img(db, sha) -> ImageRecord: - img = ImageRecord( - path=f"/images/{sha}.jpg", sha256=sha, size_bytes=1, mime="image/jpeg", - width=1, height=1, origin="imported_filesystem", integrity_status="unknown", - ) - db.add(img) - await db.flush() - return img - - def _head(tag_id): return TagHead( tag_id=tag_id, embedding_version="siglip-test", weights=[0.0] * 1152, diff --git a/tests/test_head_positive_sources.py b/tests/test_head_positive_sources.py index c26039e..f864705 100644 --- a/tests/test_head_positive_sources.py +++ b/tests/test_head_positive_sources.py @@ -8,28 +8,12 @@ from backend.app.models import ImageRecord, Tag, TagKind, TagPositiveConfirmatio from backend.app.models.tag import image_tag from backend.app.services.ml.heads import _eligible_tag_ids from backend.app.services.ml.training_data import _ids_with_tag +from tests.factories import make_image as _img +from tests.factories import make_tag as _tag pytestmark = pytest.mark.integration -def _img(db, sha: str) -> ImageRecord: - img = ImageRecord( - path=f"/images/{sha}.jpg", sha256=sha, size_bytes=1, mime="image/jpeg", - width=1, height=1, origin="imported_filesystem", - integrity_status="unknown", - ) - db.add(img) - db.flush() - return img - - -def _tag(db, name: str) -> Tag: - t = Tag(name=name, kind=TagKind.general) - db.add(t) - db.flush() - return t - - def _apply(db, image_id: int, tag_id: int, source: str) -> None: db.execute(image_tag.insert().values( image_record_id=image_id, tag_id=tag_id, source=source, diff --git a/tests/test_ml_suggestions.py b/tests/test_ml_suggestions.py index 1091518..9a44d7c 100644 --- a/tests/test_ml_suggestions.py +++ b/tests/test_ml_suggestions.py @@ -10,6 +10,7 @@ from backend.app.services.ml.allowlist import AllowlistService from backend.app.services.ml.heads import ground_applied_tag from backend.app.services.ml.suggestions import SuggestionService from backend.app.services.tag_service import TagService +from tests.factories import make_image_async as _img pytestmark = pytest.mark.integration @@ -22,17 +23,6 @@ def _emb(slot: int, val: float = 3.0) -> list[float]: return v -async def _img(db, sha: str, emb=None) -> ImageRecord: - img = ImageRecord( - path=f"/images/{sha}.jpg", sha256=sha, size_bytes=1, mime="image/jpeg", - width=1, height=1, origin="imported_filesystem", - integrity_status="unknown", siglip_embedding=emb, - ) - db.add(img) - await db.flush() - return img - - async def _embver(db) -> str: s = (await db.execute(select(MLSettings).where(MLSettings.id == 1))).scalar_one() return s.embedder_model_version diff --git a/tests/test_presentation_auto_apply.py b/tests/test_presentation_auto_apply.py index c549fbb..26775b0 100644 --- a/tests/test_presentation_auto_apply.py +++ b/tests/test_presentation_auto_apply.py @@ -17,6 +17,7 @@ from backend.app.services.ml.heads import ( auto_apply_sweep, system_tag_auto_apply_sweep, ) +from tests.factories import make_image as _img pytestmark = pytest.mark.integration @@ -27,17 +28,6 @@ def _emb(slot: int) -> list[float]: return v -def _img(db, sha: str, emb) -> ImageRecord: - img = ImageRecord( - path=f"/images/{sha}.jpg", sha256=sha, size_bytes=1, mime="image/jpeg", - width=1, height=1, origin="imported_filesystem", - integrity_status="unknown", siglip_embedding=emb, - ) - db.add(img) - db.flush() - return img - - def _head(db, tag_id: int, slot: int, *, weight=1.0): # weight 3.0 → score sigmoid(3)=0.95 clears the 0.90 presentation floor; # weight 1.0 → sigmoid(1)=0.73 clears the 0.50 conflict floor. diff --git a/tests/test_process_auto_apply.py b/tests/test_process_auto_apply.py index 1ed5609..b8db16c 100644 --- a/tests/test_process_auto_apply.py +++ b/tests/test_process_auto_apply.py @@ -16,6 +16,7 @@ from backend.app.models import ( from backend.app.models.tag import image_tag from backend.app.services.ml.heads import system_tag_auto_apply_sweep from backend.app.services.ml.training_data import _ids_with_tag +from tests.factories import make_image as _img pytestmark = pytest.mark.integration @@ -26,17 +27,6 @@ def _emb(slot: int) -> list[float]: return v -def _img(db, sha: str, emb) -> ImageRecord: - img = ImageRecord( - path=f"/images/{sha}.jpg", sha256=sha, size_bytes=1, mime="image/jpeg", - width=1, height=1, origin="imported_filesystem", - integrity_status="unknown", siglip_embedding=emb, - ) - db.add(img) - db.flush() - return img - - def _head(db, tag_id: int, slot: int, *, weight=1.0): s = db.execute(select(MLSettings).where(MLSettings.id == 1)).scalar_one() w = [0.0] * 1152 diff --git a/tests/test_regions.py b/tests/test_regions.py index f060d77..a058628 100644 --- a/tests/test_regions.py +++ b/tests/test_regions.py @@ -3,20 +3,11 @@ import pytest from backend.app.models import ImageRecord from backend.app.services.ml.regions import RegionService +from tests.factories import make_image_async as _img pytestmark = pytest.mark.integration -async def _img(db, sha) -> ImageRecord: - img = ImageRecord( - path=f"/images/{sha}.jpg", sha256=sha, size_bytes=1, mime="image/jpeg", - width=1, height=1, origin="imported_filesystem", integrity_status="unknown", - ) - db.add(img) - await db.flush() - return img - - @pytest.mark.asyncio async def test_replace_and_get_regions(db): img = await _img(db, "a" * 64) diff --git a/tests/test_suggestions_bulk.py b/tests/test_suggestions_bulk.py index 5df13f2..431b367 100644 --- a/tests/test_suggestions_bulk.py +++ b/tests/test_suggestions_bulk.py @@ -7,6 +7,7 @@ from backend.app.models import ImageRecord, MLSettings, TagHead, TagKind from backend.app.models.tag import image_tag from backend.app.services.ml.suggestions import SuggestionService from backend.app.services.tag_service import TagService +from tests.factories import make_image_async as _img pytestmark = pytest.mark.integration @@ -17,17 +18,6 @@ def _emb(slot: int) -> list[float]: return v -async def _img(db, sha: str, emb=None) -> ImageRecord: - img = ImageRecord( - path=f"/images/{sha}.jpg", sha256=sha, size_bytes=1, mime="image/jpeg", - width=1, height=1, origin="imported_filesystem", - integrity_status="unknown", siglip_embedding=emb, - ) - db.add(img) - await db.flush() - return img - - async def _head(db, tag_id: int, slot: int = 0): s = (await db.execute(select(MLSettings).where(MLSettings.id == 1))).scalar_one() weights = [0.0] * 1152