Skip to content

Commit 33b18fc

Browse files
committed
fix: require explicit Hugging Face gated responses
1 parent d33c1db commit 33b18fc

5 files changed

Lines changed: 43 additions & 167 deletions

File tree

browser_tests/assets/missing/missing_models_gated_unsupported.json

Lines changed: 0 additions & 92 deletions
This file was deleted.

browser_tests/tests/propertiesPanel/errorsTabMissingModels.spec.ts

Lines changed: 5 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,6 @@ import {
1414

1515
const FAKE_MODEL_NAME = 'fake_model.safetensors'
1616
const GATED_MODEL_REPO_URL = 'https://huggingface.co/comfy-e2e/gated-test'
17-
const UNSUPPORTED_MODEL_NAME = 'gated_model.bin'
1817

1918
function getModelLabel(group: Locator, modelName: string = FAKE_MODEL_NAME) {
2019
return group.getByRole('button', { name: modelName, exact: true })
@@ -171,7 +170,11 @@ test.describe('Errors tab - Missing models', { tag: '@ui' }, () => {
171170
if (route.request().method() === 'HEAD') {
172171
return route.fulfill({
173172
status: 403,
174-
headers: { 'Access-Control-Allow-Origin': '*' }
173+
headers: {
174+
'Access-Control-Allow-Origin': '*',
175+
'Access-Control-Expose-Headers': 'X-Error-Code',
176+
'X-Error-Code': 'GatedRepo'
177+
}
175178
})
176179
}
177180

@@ -224,62 +227,5 @@ test.describe('Errors tab - Missing models', { tag: '@ui' }, () => {
224227
await expect(accessPage).toHaveURL(GATED_MODEL_REPO_URL)
225228
})
226229
})
227-
228-
test.describe('Gated model with an unsupported extension', () => {
229-
test('Should not show gated guidance when no row can act on it', async ({
230-
comfyPage
231-
}) => {
232-
let releaseGatedHead: () => void
233-
const gatedHeadServed = new Promise<void>((resolve) => {
234-
releaseGatedHead = resolve
235-
})
236-
237-
await comfyPage.page
238-
.context()
239-
.route('https://huggingface.co/**', async (route) => {
240-
if (route.request().method() !== 'HEAD') return route.abort()
241-
242-
if (route.request().url().endsWith('.bin')) {
243-
await route.fulfill({
244-
status: 403,
245-
headers: { 'Access-Control-Allow-Origin': '*' }
246-
})
247-
releaseGatedHead()
248-
return
249-
}
250-
251-
await gatedHeadServed
252-
return route.fulfill({
253-
status: 200,
254-
headers: {
255-
'Access-Control-Allow-Origin': '*',
256-
'Access-Control-Expose-Headers': 'content-length',
257-
'content-length': '1024'
258-
}
259-
})
260-
})
261-
await loadWorkflowAndOpenErrorsTab(
262-
comfyPage,
263-
'missing/missing_models_gated_unsupported'
264-
)
265-
266-
const modelsGroup = comfyPage.page.getByTestId(
267-
TestIds.dialogs.missingModelsGroup
268-
)
269-
await expect(
270-
getModelLabel(modelsGroup, UNSUPPORTED_MODEL_NAME)
271-
).toBeVisible()
272-
await expect(
273-
modelsGroup.getByText('checkpoints · 1 KB', { exact: true })
274-
).toBeVisible()
275-
276-
await expect(
277-
comfyPage.page.getByTestId(TestIds.dialogs.missingModelGatedAccess)
278-
).toHaveCount(0)
279-
await expect(
280-
comfyPage.page.getByTestId(TestIds.dialogs.missingModelGatedHint)
281-
).toHaveCount(0)
282-
})
283-
})
284230
})
285231
})

src/platform/missingModel/components/MissingModelRow.test.ts

Lines changed: 18 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { createPinia, setActivePinia } from 'pinia'
22
import { render, screen, waitFor } from '@testing-library/vue'
33
import userEvent from '@testing-library/user-event'
4-
import { beforeEach, describe, expect, it, vi } from 'vitest'
4+
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
55
import { nextTick } from 'vue'
66
import { createI18n } from 'vue-i18n'
77

@@ -190,6 +190,10 @@ describe('MissingModelRow', () => {
190190
})
191191
})
192192

193+
afterEach(() => {
194+
vi.restoreAllMocks()
195+
})
196+
193197
it('opens the model import dialog from the cloud row', async () => {
194198
const user = userEvent.setup()
195199
renderRow(makeModel([{ nodeId: '1', widgetName: 'ckpt_name' }]))
@@ -468,14 +472,20 @@ describe('MissingModelRow', () => {
468472
)
469473
})
470474

471-
it('delegates the gated repo action to the Desktop bridge when available', async () => {
475+
it('delegates the gated repo action to the Desktop bridge with its receiver', async () => {
472476
mockIsCloud.value = false
473477
const user = userEvent.setup()
474-
const openModelAccessPage = vi.fn().mockResolvedValue(true)
475-
window.__comfyDesktop2 = {
476-
isRemote: () => false,
477-
openModelAccessPage
478+
let receiver: unknown
479+
let openedUrl: string | undefined
480+
const bridge = {
481+
isRemote: () => true,
482+
async openModelAccessPage(this: unknown, url: string) {
483+
receiver = this
484+
openedUrl = url
485+
return true
486+
}
478487
}
488+
window.__comfyDesktop2 = bridge
479489
const model = makeModel([{ nodeId: '1', widgetName: 'ckpt_name' }])
480490
model.representative.url =
481491
'https://huggingface.co/bfl/FLUX.1/resolve/main/model.safetensors'
@@ -490,9 +500,8 @@ describe('MissingModelRow', () => {
490500

491501
await user.click(screen.getByTestId('missing-model-gated-access'))
492502

493-
expect(openModelAccessPage).toHaveBeenCalledWith(
494-
'https://huggingface.co/bfl/FLUX.1'
495-
)
503+
expect(receiver).toBe(bridge)
504+
expect(openedUrl).toBe('https://huggingface.co/bfl/FLUX.1')
496505
expect(mockOpenGatedRepoPage).not.toHaveBeenCalled()
497506
})
498507

@@ -559,7 +568,6 @@ describe('MissingModelRow', () => {
559568
'Failed to open model access page in Desktop:',
560569
error
561570
)
562-
consoleErrorSpy.mockRestore()
563571
})
564572

565573
it('shows unknown category metadata for models without a directory', () => {

src/platform/missingModel/missingModelDownload.test.ts

Lines changed: 17 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -93,7 +93,11 @@ describe('fetchModelMetadata', () => {
9393
it.for([401, 403, 451])(
9494
'returns gatedRepoUrl for gated HuggingFace HEAD requests (%s)',
9595
async (status) => {
96-
fetchMock.mockResolvedValueOnce({ ok: false, status })
96+
fetchMock.mockResolvedValueOnce({
97+
ok: false,
98+
status,
99+
headers: new Headers({ 'x-error-code': 'GatedRepo' })
100+
})
97101

98102
const metadata = await fetchModelMetadata(
99103
`https://huggingface.co/bfl/FLUX.1/resolve/main/gated-${status}-${testId}.safetensors`
@@ -103,10 +107,14 @@ describe('fetchModelMetadata', () => {
103107
}
104108
)
105109

106-
it.for([404, 500])(
107-
'does not treat HuggingFace %s as gated',
110+
it.for([401, 403, 451, 404, 500])(
111+
'does not treat HuggingFace %s as gated without the GatedRepo error code',
108112
async (status) => {
109-
fetchMock.mockResolvedValueOnce({ ok: false, status })
113+
fetchMock.mockResolvedValueOnce({
114+
ok: false,
115+
status,
116+
headers: new Headers()
117+
})
110118

111119
const metadata = await fetchModelMetadata(
112120
`https://huggingface.co/org/model/resolve/main/not-gated-${status}-${testId}.safetensors`
@@ -117,7 +125,11 @@ describe('fetchModelMetadata', () => {
117125
)
118126

119127
it('does not treat non-HuggingFace hosts as gated', async () => {
120-
fetchMock.mockResolvedValueOnce({ ok: false, status: 403 })
128+
fetchMock.mockResolvedValueOnce({
129+
ok: false,
130+
status: 403,
131+
headers: new Headers({ 'x-error-code': 'GatedRepo' })
132+
})
121133

122134
const metadata = await fetchModelMetadata(
123135
`https://huggingface.co.evil.com/org/model/resolve/main/gated-${testId}.safetensors`

src/platform/missingModel/missingModelDownload.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -166,6 +166,7 @@ async function fetchCivitaiMetadata(url: string): Promise<ModelMetadata> {
166166
}
167167

168168
const GATED_STATUS_CODES = new Set([401, 403, 451])
169+
const HUGGING_FACE_GATED_ERROR_CODE = 'GatedRepo'
169170

170171
async function fetchHeadMetadata(url: string): Promise<ModelMetadata> {
171172
try {
@@ -174,7 +175,8 @@ async function fetchHeadMetadata(url: string): Promise<ModelMetadata> {
174175
if (!response.ok) {
175176
if (
176177
isHuggingFaceRepoUrl(url) &&
177-
GATED_STATUS_CODES.has(response.status)
178+
GATED_STATUS_CODES.has(response.status) &&
179+
response.headers.get('x-error-code') === HUGGING_FACE_GATED_ERROR_CODE
178180
) {
179181
return { fileSize: null, gatedRepoUrl: downloadUrlToHfRepoUrl(url) }
180182
}

0 commit comments

Comments
 (0)