mirror of
https://github.com/willmiao/ComfyUI-Lora-Manager.git
synced 2026-06-17 07:59:24 -03:00
fix: actually halt bulk operations on cancel — frontend AbortController + backend guards (#986)
This commit is contained in:
@@ -22,6 +22,7 @@
|
|||||||
},
|
},
|
||||||
"status": {
|
"status": {
|
||||||
"loading": "Wird geladen...",
|
"loading": "Wird geladen...",
|
||||||
|
"cancelling": "Abbrechen...",
|
||||||
"unknown": "Unbekannt",
|
"unknown": "Unbekannt",
|
||||||
"date": "Datum",
|
"date": "Datum",
|
||||||
"version": "Version",
|
"version": "Version",
|
||||||
|
|||||||
@@ -22,6 +22,7 @@
|
|||||||
},
|
},
|
||||||
"status": {
|
"status": {
|
||||||
"loading": "Loading...",
|
"loading": "Loading...",
|
||||||
|
"cancelling": "Cancelling...",
|
||||||
"unknown": "Unknown",
|
"unknown": "Unknown",
|
||||||
"date": "Date",
|
"date": "Date",
|
||||||
"version": "Version",
|
"version": "Version",
|
||||||
|
|||||||
@@ -22,6 +22,7 @@
|
|||||||
},
|
},
|
||||||
"status": {
|
"status": {
|
||||||
"loading": "Cargando...",
|
"loading": "Cargando...",
|
||||||
|
"cancelling": "Cancelando...",
|
||||||
"unknown": "Desconocido",
|
"unknown": "Desconocido",
|
||||||
"date": "Fecha",
|
"date": "Fecha",
|
||||||
"version": "Versión",
|
"version": "Versión",
|
||||||
|
|||||||
@@ -22,6 +22,7 @@
|
|||||||
},
|
},
|
||||||
"status": {
|
"status": {
|
||||||
"loading": "Chargement...",
|
"loading": "Chargement...",
|
||||||
|
"cancelling": "Annulation...",
|
||||||
"unknown": "Inconnu",
|
"unknown": "Inconnu",
|
||||||
"date": "Date",
|
"date": "Date",
|
||||||
"version": "Version",
|
"version": "Version",
|
||||||
|
|||||||
@@ -22,6 +22,7 @@
|
|||||||
},
|
},
|
||||||
"status": {
|
"status": {
|
||||||
"loading": "טוען...",
|
"loading": "טוען...",
|
||||||
|
"cancelling": "מבטל...",
|
||||||
"unknown": "לא ידוע",
|
"unknown": "לא ידוע",
|
||||||
"date": "תאריך",
|
"date": "תאריך",
|
||||||
"version": "גרסה",
|
"version": "גרסה",
|
||||||
|
|||||||
@@ -22,6 +22,7 @@
|
|||||||
},
|
},
|
||||||
"status": {
|
"status": {
|
||||||
"loading": "読み込み中...",
|
"loading": "読み込み中...",
|
||||||
|
"cancelling": "キャンセル中...",
|
||||||
"unknown": "不明",
|
"unknown": "不明",
|
||||||
"date": "日付",
|
"date": "日付",
|
||||||
"version": "バージョン",
|
"version": "バージョン",
|
||||||
|
|||||||
@@ -22,6 +22,7 @@
|
|||||||
},
|
},
|
||||||
"status": {
|
"status": {
|
||||||
"loading": "로딩 중...",
|
"loading": "로딩 중...",
|
||||||
|
"cancelling": "취소 중...",
|
||||||
"unknown": "알 수 없음",
|
"unknown": "알 수 없음",
|
||||||
"date": "날짜",
|
"date": "날짜",
|
||||||
"version": "버전",
|
"version": "버전",
|
||||||
|
|||||||
@@ -22,6 +22,7 @@
|
|||||||
},
|
},
|
||||||
"status": {
|
"status": {
|
||||||
"loading": "Загрузка...",
|
"loading": "Загрузка...",
|
||||||
|
"cancelling": "Отмена...",
|
||||||
"unknown": "Неизвестно",
|
"unknown": "Неизвестно",
|
||||||
"date": "Дата",
|
"date": "Дата",
|
||||||
"version": "Версия",
|
"version": "Версия",
|
||||||
|
|||||||
@@ -22,6 +22,7 @@
|
|||||||
},
|
},
|
||||||
"status": {
|
"status": {
|
||||||
"loading": "加载中...",
|
"loading": "加载中...",
|
||||||
|
"cancelling": "取消中...",
|
||||||
"unknown": "未知",
|
"unknown": "未知",
|
||||||
"date": "日期",
|
"date": "日期",
|
||||||
"version": "版本",
|
"version": "版本",
|
||||||
|
|||||||
@@ -22,6 +22,7 @@
|
|||||||
},
|
},
|
||||||
"status": {
|
"status": {
|
||||||
"loading": "載入中...",
|
"loading": "載入中...",
|
||||||
|
"cancelling": "取消中...",
|
||||||
"unknown": "未知",
|
"unknown": "未知",
|
||||||
"date": "日期",
|
"date": "日期",
|
||||||
"version": "版本",
|
"version": "版本",
|
||||||
|
|||||||
@@ -532,6 +532,13 @@ class ModelScanner:
|
|||||||
if not scan_result or not getattr(self, '_persistent_cache', None):
|
if not scan_result or not getattr(self, '_persistent_cache', None):
|
||||||
return
|
return
|
||||||
|
|
||||||
|
if self.is_cancelled():
|
||||||
|
logger.info(
|
||||||
|
f"{self.model_type.capitalize()} Scanner: Skipping _save_persistent_cache "
|
||||||
|
"after cancellation"
|
||||||
|
)
|
||||||
|
return
|
||||||
|
|
||||||
hash_snapshot = self._build_hash_index_snapshot(scan_result.hash_index)
|
hash_snapshot = self._build_hash_index_snapshot(scan_result.hash_index)
|
||||||
loop = asyncio.get_event_loop()
|
loop = asyncio.get_event_loop()
|
||||||
try:
|
try:
|
||||||
@@ -705,14 +712,20 @@ class ModelScanner:
|
|||||||
# Determine the page type based on model type
|
# Determine the page type based on model type
|
||||||
# Scan for new data
|
# Scan for new data
|
||||||
scan_result = await self._gather_model_data()
|
scan_result = await self._gather_model_data()
|
||||||
await self._apply_scan_result(scan_result)
|
if not self.is_cancelled():
|
||||||
await self._save_persistent_cache(scan_result)
|
await self._apply_scan_result(scan_result)
|
||||||
await self._sync_download_history(scan_result.raw_data, source='scan')
|
await self._save_persistent_cache(scan_result)
|
||||||
|
await self._sync_download_history(scan_result.raw_data, source='scan')
|
||||||
|
|
||||||
logger.info(
|
logger.info(
|
||||||
f"{self.model_type.capitalize()} Scanner: Cache initialization completed in {time.time() - start_time:.2f} seconds, "
|
f"{self.model_type.capitalize()} Scanner: Cache initialization completed in {time.time() - start_time:.2f} seconds, "
|
||||||
f"found {len(scan_result.raw_data)} models"
|
f"found {len(scan_result.raw_data)} models"
|
||||||
)
|
)
|
||||||
|
else:
|
||||||
|
logger.info(
|
||||||
|
f"{self.model_type.capitalize()} Scanner: Cache initialization cancelled "
|
||||||
|
f"after {time.time() - start_time:.2f} seconds"
|
||||||
|
)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"{self.model_type.capitalize()} Scanner: Error initializing cache: {e}")
|
logger.error(f"{self.model_type.capitalize()} Scanner: Error initializing cache: {e}")
|
||||||
# Ensure cache is at least an empty structure on error
|
# Ensure cache is at least an empty structure on error
|
||||||
@@ -1096,6 +1109,13 @@ class ModelScanner:
|
|||||||
if scan_result is None:
|
if scan_result is None:
|
||||||
return
|
return
|
||||||
|
|
||||||
|
if self.is_cancelled():
|
||||||
|
logger.info(
|
||||||
|
f"{self.model_type.capitalize()} Scanner: Skipping _apply_scan_result "
|
||||||
|
"after cancellation"
|
||||||
|
)
|
||||||
|
return
|
||||||
|
|
||||||
self._hash_index = scan_result.hash_index
|
self._hash_index = scan_result.hash_index
|
||||||
self._tags_count = dict(scan_result.tags_count)
|
self._tags_count = dict(scan_result.tags_count)
|
||||||
self._excluded_models = list(scan_result.excluded_models)
|
self._excluded_models = list(scan_result.excluded_models)
|
||||||
@@ -1764,6 +1784,13 @@ class ModelScanner:
|
|||||||
"""
|
"""
|
||||||
if not file_paths or self._cache is None:
|
if not file_paths or self._cache is None:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
if self.is_cancelled():
|
||||||
|
logger.info(
|
||||||
|
f"{self.model_type.capitalize()} Scanner: Skipping cache update "
|
||||||
|
"after cancelled bulk delete"
|
||||||
|
)
|
||||||
|
return False
|
||||||
|
|
||||||
try:
|
try:
|
||||||
# Get all models that need to be removed from cache
|
# Get all models that need to be removed from cache
|
||||||
|
|||||||
@@ -468,17 +468,21 @@ export class BaseModelApiClient {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async refreshModels(fullRebuild = false) {
|
async refreshModels(fullRebuild = false) {
|
||||||
|
const abortController = new AbortController();
|
||||||
try {
|
try {
|
||||||
state.loadingManager.show(
|
state.loadingManager.show(
|
||||||
`${fullRebuild ? 'Full rebuild' : 'Refreshing'} ${this.apiConfig.config.displayName}s...`,
|
`${fullRebuild ? 'Full rebuild' : 'Refreshing'} ${this.apiConfig.config.displayName}s...`,
|
||||||
0
|
0
|
||||||
);
|
);
|
||||||
state.loadingManager.showCancelButton(() => this.cancelTask());
|
state.loadingManager.showCancelButton(() => {
|
||||||
|
this.cancelTask();
|
||||||
|
abortController.abort();
|
||||||
|
});
|
||||||
|
|
||||||
const url = new URL(this.apiConfig.endpoints.scan, window.location.origin);
|
const url = new URL(this.apiConfig.endpoints.scan, window.location.origin);
|
||||||
url.searchParams.append('full_rebuild', fullRebuild);
|
url.searchParams.append('full_rebuild', fullRebuild);
|
||||||
|
|
||||||
const response = await fetch(url);
|
const response = await fetch(url, { signal: abortController.signal });
|
||||||
|
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
throw new Error(`Failed to refresh ${this.apiConfig.config.displayName}s: ${response.status} ${response.statusText}`);
|
throw new Error(`Failed to refresh ${this.apiConfig.config.displayName}s: ${response.status} ${response.statusText}`);
|
||||||
@@ -494,6 +498,10 @@ export class BaseModelApiClient {
|
|||||||
|
|
||||||
showToast('toast.api.refreshComplete', { action: fullRebuild ? 'Full rebuild' : 'Refresh' }, 'success');
|
showToast('toast.api.refreshComplete', { action: fullRebuild ? 'Full rebuild' : 'Refresh' }, 'success');
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
if (error.name === 'AbortError') {
|
||||||
|
showToast('toast.api.operationCancelled', {}, 'info');
|
||||||
|
return;
|
||||||
|
}
|
||||||
console.error('Refresh failed:', error);
|
console.error('Refresh failed:', error);
|
||||||
showToast('toast.api.refreshFailed', { action: fullRebuild ? 'rebuild' : 'refresh', type: this.apiConfig.config.displayName }, 'error');
|
showToast('toast.api.refreshFailed', { action: fullRebuild ? 'rebuild' : 'refresh', type: this.apiConfig.config.displayName }, 'error');
|
||||||
} finally {
|
} finally {
|
||||||
@@ -948,13 +956,19 @@ export class BaseModelApiClient {
|
|||||||
throw new Error('No model IDs provided');
|
throw new Error('No model IDs provided');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const abortController = new AbortController();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
state.loadingManager.show('Checking for updates...', 0);
|
state.loadingManager.show('Checking for updates...', 0);
|
||||||
state.loadingManager.showCancelButton(() => this.cancelTask());
|
state.loadingManager.showCancelButton(() => {
|
||||||
|
this.cancelTask();
|
||||||
|
abortController.abort();
|
||||||
|
});
|
||||||
|
|
||||||
const response = await fetch(this.apiConfig.endpoints.refreshUpdates, {
|
const response = await fetch(this.apiConfig.endpoints.refreshUpdates, {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: { 'Content-Type': 'application/json' },
|
headers: { 'Content-Type': 'application/json' },
|
||||||
|
signal: abortController.signal,
|
||||||
body: JSON.stringify({
|
body: JSON.stringify({
|
||||||
model_ids: modelIds,
|
model_ids: modelIds,
|
||||||
force
|
force
|
||||||
@@ -979,6 +993,10 @@ export class BaseModelApiClient {
|
|||||||
|
|
||||||
return payload;
|
return payload;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
if (error.name === 'AbortError') {
|
||||||
|
showToast('toast.api.operationCancelled', {}, 'info');
|
||||||
|
return null;
|
||||||
|
}
|
||||||
console.error('Error refreshing updates for models:', error);
|
console.error('Error refreshing updates for models:', error);
|
||||||
throw error;
|
throw error;
|
||||||
} finally {
|
} finally {
|
||||||
@@ -991,13 +1009,19 @@ export class BaseModelApiClient {
|
|||||||
throw new Error('No folder path provided');
|
throw new Error('No folder path provided');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const abortController = new AbortController();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
state.loadingManager.show('Checking for updates...', 0);
|
state.loadingManager.show('Checking for updates...', 0);
|
||||||
state.loadingManager.showCancelButton(() => this.cancelTask());
|
state.loadingManager.showCancelButton(() => {
|
||||||
|
this.cancelTask();
|
||||||
|
abortController.abort();
|
||||||
|
});
|
||||||
|
|
||||||
const response = await fetch(this.apiConfig.endpoints.refreshUpdates, {
|
const response = await fetch(this.apiConfig.endpoints.refreshUpdates, {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: { 'Content-Type': 'application/json' },
|
headers: { 'Content-Type': 'application/json' },
|
||||||
|
signal: abortController.signal,
|
||||||
body: JSON.stringify({
|
body: JSON.stringify({
|
||||||
folder_path: folderPath,
|
folder_path: folderPath,
|
||||||
force
|
force
|
||||||
@@ -1022,6 +1046,10 @@ export class BaseModelApiClient {
|
|||||||
|
|
||||||
return payload;
|
return payload;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
if (error.name === 'AbortError') {
|
||||||
|
showToast('toast.api.operationCancelled', {}, 'info');
|
||||||
|
return null;
|
||||||
|
}
|
||||||
console.error('Error refreshing updates for folder:', error);
|
console.error('Error refreshing updates for folder:', error);
|
||||||
throw error;
|
throw error;
|
||||||
} finally {
|
} finally {
|
||||||
@@ -1471,15 +1499,21 @@ export class BaseModelApiClient {
|
|||||||
throw new Error('No file paths provided');
|
throw new Error('No file paths provided');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const abortController = new AbortController();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
state.loadingManager.showSimpleLoading(`Deleting ${this.apiConfig.config.displayName.toLowerCase()}s...`);
|
state.loadingManager.showSimpleLoading(`Deleting ${this.apiConfig.config.displayName.toLowerCase()}s...`);
|
||||||
state.loadingManager.showCancelButton(() => this.cancelTask());
|
state.loadingManager.showCancelButton(() => {
|
||||||
|
this.cancelTask();
|
||||||
|
abortController.abort();
|
||||||
|
});
|
||||||
|
|
||||||
const response = await fetch(this.apiConfig.endpoints.bulkDelete, {
|
const response = await fetch(this.apiConfig.endpoints.bulkDelete, {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: {
|
headers: {
|
||||||
'Content-Type': 'application/json'
|
'Content-Type': 'application/json'
|
||||||
},
|
},
|
||||||
|
signal: abortController.signal,
|
||||||
body: JSON.stringify({
|
body: JSON.stringify({
|
||||||
file_paths: filePaths
|
file_paths: filePaths
|
||||||
})
|
})
|
||||||
@@ -1502,6 +1536,10 @@ export class BaseModelApiClient {
|
|||||||
throw new Error(result.error || `Failed to delete ${this.apiConfig.config.displayName.toLowerCase()}s`);
|
throw new Error(result.error || `Failed to delete ${this.apiConfig.config.displayName.toLowerCase()}s`);
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
if (error.name === 'AbortError') {
|
||||||
|
console.log(`Bulk delete cancelled by user for ${this.apiConfig.config.displayName.toLowerCase()}s`);
|
||||||
|
return { success: false, cancelled: true };
|
||||||
|
}
|
||||||
console.error(`Error during bulk delete of ${this.apiConfig.config.displayName.toLowerCase()}s:`, error);
|
console.error(`Error during bulk delete of ${this.apiConfig.config.displayName.toLowerCase()}s:`, error);
|
||||||
throw error;
|
throw error;
|
||||||
} finally {
|
} finally {
|
||||||
|
|||||||
@@ -611,7 +611,9 @@ export class BulkManager {
|
|||||||
|
|
||||||
const result = await apiClient.bulkDeleteModels(filePaths);
|
const result = await apiClient.bulkDeleteModels(filePaths);
|
||||||
|
|
||||||
if (result.success) {
|
if (result?.cancelled) {
|
||||||
|
showToast('toast.api.operationCancelled', {}, 'info');
|
||||||
|
} else if (result.success) {
|
||||||
const currentConfig = this.getCurrentDisplayConfig();
|
const currentConfig = this.getCurrentDisplayConfig();
|
||||||
showToast('toast.models.deletedSuccessfully', {
|
showToast('toast.models.deletedSuccessfully', {
|
||||||
count: result.deleted_count,
|
count: result.deleted_count,
|
||||||
|
|||||||
@@ -73,7 +73,7 @@ export class LoadingManager {
|
|||||||
if (this.onCancelCallback) {
|
if (this.onCancelCallback) {
|
||||||
this.onCancelCallback();
|
this.onCancelCallback();
|
||||||
this.cancelButton.disabled = true;
|
this.cancelButton.disabled = true;
|
||||||
this.cancelButton.textContent = translate('common.status.loading', {}, 'Loading...');
|
this.cancelButton.textContent = translate('common.status.cancelling', {}, 'Cancelling...');
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -42,7 +42,12 @@ export async function performModelUpdateCheck({ onStart, onComplete } = {}) {
|
|||||||
onStart?.({ displayName, loadingMessage });
|
onStart?.({ displayName, loadingMessage });
|
||||||
|
|
||||||
state.loadingManager?.showSimpleLoading?.(loadingMessage);
|
state.loadingManager?.showSimpleLoading?.(loadingMessage);
|
||||||
state.loadingManager?.showCancelButton?.(() => apiClient.cancelTask());
|
|
||||||
|
const abortController = new AbortController();
|
||||||
|
state.loadingManager?.showCancelButton?.(() => {
|
||||||
|
apiClient.cancelTask();
|
||||||
|
abortController.abort();
|
||||||
|
});
|
||||||
|
|
||||||
let status = 'success';
|
let status = 'success';
|
||||||
let records = [];
|
let records = [];
|
||||||
@@ -52,6 +57,7 @@ export async function performModelUpdateCheck({ onStart, onComplete } = {}) {
|
|||||||
const response = await fetch(apiConfig.endpoints.refreshUpdates, {
|
const response = await fetch(apiConfig.endpoints.refreshUpdates, {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: { 'Content-Type': 'application/json' },
|
headers: { 'Content-Type': 'application/json' },
|
||||||
|
signal: abortController.signal,
|
||||||
body: JSON.stringify({ force: false })
|
body: JSON.stringify({ force: false })
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -81,6 +87,11 @@ export async function performModelUpdateCheck({ onStart, onComplete } = {}) {
|
|||||||
|
|
||||||
await resetAndReload(false);
|
await resetAndReload(false);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
|
if (err?.name === 'AbortError') {
|
||||||
|
showToast('toast.api.operationCancelled', {}, 'info');
|
||||||
|
status = 'cancelled';
|
||||||
|
return { status: 'cancelled', displayName, records: [], error: null };
|
||||||
|
}
|
||||||
status = 'error';
|
status = 'error';
|
||||||
error = err instanceof Error ? err : new Error(String(err));
|
error = err instanceof Error ? err : new Error(String(err));
|
||||||
console.error('Error checking model updates:', error);
|
console.error('Error checking model updates:', error);
|
||||||
@@ -126,7 +137,12 @@ export async function performFolderUpdateCheck(folderPath, { onComplete } = {})
|
|||||||
);
|
);
|
||||||
|
|
||||||
state.loadingManager?.showSimpleLoading?.(loadingMessage);
|
state.loadingManager?.showSimpleLoading?.(loadingMessage);
|
||||||
state.loadingManager?.showCancelButton?.(() => apiClient.cancelTask());
|
|
||||||
|
const abortController = new AbortController();
|
||||||
|
state.loadingManager?.showCancelButton?.(() => {
|
||||||
|
apiClient.cancelTask();
|
||||||
|
abortController.abort();
|
||||||
|
});
|
||||||
|
|
||||||
let status = 'success';
|
let status = 'success';
|
||||||
let records = [];
|
let records = [];
|
||||||
@@ -136,6 +152,7 @@ export async function performFolderUpdateCheck(folderPath, { onComplete } = {})
|
|||||||
const response = await fetch(apiConfig.endpoints.refreshUpdates, {
|
const response = await fetch(apiConfig.endpoints.refreshUpdates, {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: { 'Content-Type': 'application/json' },
|
headers: { 'Content-Type': 'application/json' },
|
||||||
|
signal: abortController.signal,
|
||||||
body: JSON.stringify({ folder_path: folderPath, force: false })
|
body: JSON.stringify({ folder_path: folderPath, force: false })
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -165,6 +182,11 @@ export async function performFolderUpdateCheck(folderPath, { onComplete } = {})
|
|||||||
|
|
||||||
await resetAndReload(false);
|
await resetAndReload(false);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
|
if (err?.name === 'AbortError') {
|
||||||
|
showToast('toast.api.operationCancelled', {}, 'info');
|
||||||
|
status = 'cancelled';
|
||||||
|
return { status: 'cancelled', records: [], error: null };
|
||||||
|
}
|
||||||
status = 'error';
|
status = 'error';
|
||||||
error = err instanceof Error ? err : new Error(String(err));
|
error = err instanceof Error ? err : new Error(String(err));
|
||||||
console.error('Error checking folder model updates:', error);
|
console.error('Error checking folder model updates:', error);
|
||||||
|
|||||||
@@ -2190,6 +2190,7 @@ describe('Interaction-level regression coverage', () => {
|
|||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: { 'Content-Type': 'application/json' },
|
headers: { 'Content-Type': 'application/json' },
|
||||||
body: JSON.stringify({ force: false }),
|
body: JSON.stringify({ force: false }),
|
||||||
|
signal: expect.any(AbortSignal),
|
||||||
});
|
});
|
||||||
|
|
||||||
const updateResponse = await global.fetch.mock.results[1].value;
|
const updateResponse = await global.fetch.mock.results[1].value;
|
||||||
|
|||||||
Reference in New Issue
Block a user