diff --git a/tests/browser/full-inspector.spec.mjs b/tests/browser/full-inspector.spec.mjs index a8c6a0e..6c6e2f9 100644 --- a/tests/browser/full-inspector.spec.mjs +++ b/tests/browser/full-inspector.spec.mjs @@ -167,6 +167,16 @@ test('panel matches the concept order and copy',async({page,request})=>{ await selectFile(page,'workflow-only.png');await expect(panel.locator('.panel-heading h2')).toHaveText('workflow-only.png'); await expect(panel.locator('.chip:not(.blue)')).toHaveCount(1);await expect(panel.locator('.detail-inner > .tags button')).toHaveCount(0); await expect(panel.getByRole('button',{name:'Prompt history',exact:true})).toHaveCount(0); + // D39 mixed scopes: each field follows its own source, so the prefix and the tag chips can disagree. + const tagRow=panel.locator('.detail-inner > .tags').last(); + reads.patch=data=>{data.library={...data.library,status:'linked',category:'Subject',category_scope:'pair',tags_scope:'positive',tags:['alpha','beta']};}; + await selectFile(page,'complete.png'); + await expect(panel.locator('span.chip:not(.blue) .ca-visually-hidden')).toHaveText('Recipe category: '); + await expect(tagRow.getByRole('button')).toHaveText(['alpha','beta']); + reads.patch=data=>{data.library={...data.library,status:'linked',category:'Subject',category_scope:'positive',tags_scope:'pair',tags:['alpha','beta']};}; + await selectFile(page,'workflow-only.png'); + await expect(panel.locator('span.chip:not(.blue) .ca-visually-hidden')).toHaveText('Positive prompt category: '); + await expect(tagRow.locator('span.chip')).toHaveText(['alpha','beta']);await expect(tagRow.getByRole('button')).toHaveCount(0); // Loading and error states render in `.detail-inner` as `.small` text with the existing buttons. reads.patch=null;await page.unroute('**/images/inspect/**'); await page.route('**/images/inspect/**',route=>route.fulfill({status:500,json:{success:false,error:'broken'}})); diff --git a/tests/js/workspace-inspection-state.test.js b/tests/js/workspace-inspection-state.test.js index 8d6d7f6..92bc176 100644 --- a/tests/js/workspace-inspection-state.test.js +++ b/tests/js/workspace-inspection-state.test.js @@ -276,6 +276,10 @@ test('record rejects root path media and invalid versioned fields with retryable data => { data.file.size = -1; }, data => { data.library.positive_id = 9007199254740992; }, data => { data.metadata.prompt_graph = { status: 'limited', value: {} }; }, data => { data.resolution.sampler_id = {}; }, + data => { data.library.tags_scope = 'recipe'; }, + data => { data.library.category_scope = 'recipe'; }, + data => { delete data.library.category_scope; }, + data => { delete data.library.tags_scope; }, ]) { const data = envelope(); mutate(data); assert.throws(() => toRecord(data, image(), extractGenerationFacts), error => error instanceof TypeError && !error.refreshRequired); diff --git a/tests/test_inspection_response.py b/tests/test_inspection_response.py index 51ca086..37de619 100644 --- a/tests/test_inspection_response.py +++ b/tests/test_inspection_response.py @@ -664,6 +664,21 @@ class TestInspectionResponse(unittest.TestCase): for key in ("positive_id", "pair_id", "category", "title"): self.assertNotIn(key, result["library"]) self.assertEqual(result["library"]["tags"], ["valid"]) + # An unknown or non-string scope is dropped, never passed through. + for bad in ("recipe", 3, ["pair"]): + result = json.loads( + encode( + None, + library={ + "status": "linked", + "category_scope": bad, + "tags_scope": bad, + "tags": [], + }, + ) + ) + self.assertNotIn("category_scope", result["library"]) + self.assertNotIn("tags_scope", result["library"]) result = json.loads( encode(inspection({}), library={"status": "linked", "tags": ["猫" * 12000]}) )