-
Notifications
You must be signed in to change notification settings - Fork 2.7k
feat(request): warn when binary body file is missing on disk #8753
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
d8cb6f0
ab61b86
0f1b72c
f078a29
0eda877
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,64 @@ | ||||||||||||||||||||||||||||||||||||||
| import { useEffect, useRef, useState } from 'react'; | ||||||||||||||||||||||||||||||||||||||
| import { getAbsoluteFilePath } from 'utils/common/path'; | ||||||||||||||||||||||||||||||||||||||
| import { existsSync } from 'utils/filesystem'; | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| /** | ||||||||||||||||||||||||||||||||||||||
| * Checks whether the given file paths (resolved against `basePath`) exist on disk. | ||||||||||||||||||||||||||||||||||||||
| * | ||||||||||||||||||||||||||||||||||||||
| * The hook only reports what it observed — callers own the "should I warn" policy. | ||||||||||||||||||||||||||||||||||||||
| * A sequence counter discards results from superseded runs so rapid input changes | ||||||||||||||||||||||||||||||||||||||
| * can't leave stale results on screen. | ||||||||||||||||||||||||||||||||||||||
| * | ||||||||||||||||||||||||||||||||||||||
| * @param {string[]} paths - Relative or absolute file paths to check. | ||||||||||||||||||||||||||||||||||||||
| * @param {string} basePath - Base directory that relative paths are resolved against. | ||||||||||||||||||||||||||||||||||||||
| * When falsy, the hook stays in the 'idle' state without issuing any check. | ||||||||||||||||||||||||||||||||||||||
| * @returns {{ status: 'idle' | 'checking' | 'ready' | 'error', missingPaths: string[] }} | ||||||||||||||||||||||||||||||||||||||
| * - `'idle'` — nothing to check (empty input or no `basePath`) | ||||||||||||||||||||||||||||||||||||||
| * - `'checking'` — a check is in flight; `missingPaths` is empty | ||||||||||||||||||||||||||||||||||||||
| * - `'ready'` — check completed; `missingPaths` lists paths that don't exist | ||||||||||||||||||||||||||||||||||||||
| * - `'error'` — the check itself failed (e.g. IPC threw); `missingPaths` is empty | ||||||||||||||||||||||||||||||||||||||
| */ | ||||||||||||||||||||||||||||||||||||||
| const useMissingFileCheck = (paths, basePath) => { | ||||||||||||||||||||||||||||||||||||||
| const [state, setState] = useState({ status: 'idle', missingPaths: [] }); | ||||||||||||||||||||||||||||||||||||||
| const seqRef = useRef(0); | ||||||||||||||||||||||||||||||||||||||
| const pathsRef = useRef(paths); | ||||||||||||||||||||||||||||||||||||||
| pathsRef.current = paths; | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| // Content-based key so re-renders that pass a fresh array with the same | ||||||||||||||||||||||||||||||||||||||
| // contents don't retrigger the check. | ||||||||||||||||||||||||||||||||||||||
| const pathsKey = paths.join('\0'); | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| useEffect(() => { | ||||||||||||||||||||||||||||||||||||||
| const currentPaths = pathsRef.current; | ||||||||||||||||||||||||||||||||||||||
| if (!currentPaths.length || !basePath) { | ||||||||||||||||||||||||||||||||||||||
| setState({ status: 'idle', missingPaths: [] }); | ||||||||||||||||||||||||||||||||||||||
| return; | ||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| const seq = ++seqRef.current; | ||||||||||||||||||||||||||||||||||||||
| setState({ status: 'checking', missingPaths: [] }); | ||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+31
to
+39
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win Invalidate in-flight checks when entering idle. When Proposed fix useEffect(() => {
+ const seq = ++seqRef.current;
const currentPaths = pathsRef.current;
if (!currentPaths.length || !basePath) {
setState({ status: 'idle', missingPaths: [] });
return;
}
- const seq = ++seqRef.current;
setState({ status: 'checking', missingPaths: [] });📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| Promise.all( | ||||||||||||||||||||||||||||||||||||||
| currentPaths.map(async (filePath) => { | ||||||||||||||||||||||||||||||||||||||
| const exists = await existsSync(getAbsoluteFilePath(basePath, filePath)); | ||||||||||||||||||||||||||||||||||||||
| return { filePath, exists }; | ||||||||||||||||||||||||||||||||||||||
| }) | ||||||||||||||||||||||||||||||||||||||
| ).then( | ||||||||||||||||||||||||||||||||||||||
| (results) => { | ||||||||||||||||||||||||||||||||||||||
| if (seq !== seqRef.current) return; | ||||||||||||||||||||||||||||||||||||||
| setState({ | ||||||||||||||||||||||||||||||||||||||
| status: 'ready', | ||||||||||||||||||||||||||||||||||||||
| missingPaths: results.filter((r) => !r.exists).map((r) => r.filePath) | ||||||||||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||||||||||
| }, | ||||||||||||||||||||||||||||||||||||||
| () => { | ||||||||||||||||||||||||||||||||||||||
| if (seq !== seqRef.current) return; | ||||||||||||||||||||||||||||||||||||||
| setState({ status: 'error', missingPaths: [] }); | ||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||
| ); | ||||||||||||||||||||||||||||||||||||||
| }, [pathsKey, basePath]); | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| return state; | ||||||||||||||||||||||||||||||||||||||
| }; | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| export default useMissingFileCheck; | ||||||||||||||||||||||||||||||||||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,57 @@ | ||
| import * as path from 'path'; | ||
| import { test, expect } from '../../../playwright'; | ||
| import { closeAllCollections, mockBrowseFiles, openCollection, selectRequestPaneTab } from '../../utils/page'; | ||
| import { buildCommonLocators } from '../../utils/page/locators'; | ||
|
|
||
| const MISSING_FILE_WARNING = 'The file above is not in the given directory, please upload it again.'; | ||
|
|
||
| test.describe('File / Binary body missing-file warning', () => { | ||
| test.afterAll(async ({ page }) => { | ||
| await closeAllCollections(page); | ||
| }); | ||
|
|
||
| test('should show warning for missing binary file', async ({ page, electronApp, createTmpDir }) => { | ||
| const collectionFile = path.resolve(__dirname, 'fixtures', 'binary-file-missing-warning.yml'); | ||
| const locators = buildCommonLocators(page); | ||
|
|
||
| const importDir = await createTmpDir('imported-collection'); | ||
|
|
||
| await mockBrowseFiles(electronApp, [importDir]); | ||
|
|
||
| await test.step('Import collection', async () => { | ||
| await locators.plusMenu.button().click(); | ||
| await locators.plusMenu.importCollection().click(); | ||
| const importModal = locators.import.modal(); | ||
| await importModal.waitFor({ state: 'visible' }); | ||
| await locators.import.fileInput().setInputFiles(collectionFile); | ||
| await locators.import.locationModal().waitFor({ state: 'visible', timeout: 5000 }); | ||
| const locationModal = locators.import.locationModal(); | ||
| await expect(locationModal.getByText('Binary body type')).toBeVisible(); | ||
| await locators.import.browseLink(locationModal).click(); | ||
| await locators.import.importButton(locationModal).click(); | ||
| await locationModal.waitFor({ state: 'hidden' }); | ||
| await expect(locators.sidebar.collection('Binary body type')).toBeVisible(); | ||
| await openCollection(page, 'Binary body type'); | ||
| }); | ||
|
|
||
| await test.step('shows a warning when the binary file does not exist on disk', async () => { | ||
| await locators.sidebar.request('Binary body - missing file').click(); | ||
| await expect(locators.request.pane()).toBeVisible(); | ||
| await selectRequestPaneTab(page, 'Body'); | ||
|
|
||
| const filePicker = page.locator('.file-picker-selected').first(); | ||
| await expect(filePicker).toBeVisible({ timeout: 5000 }); | ||
| await expect(filePicker.locator('.file-name')).toHaveText('missing-payload.bin'); | ||
|
|
||
| // Warning: the missing-file styling, icon and tooltip must be present. | ||
| await expect(filePicker).toHaveClass(/has-warning/); | ||
| const warningIcon = filePicker.locator('.warning-icon'); | ||
| await expect(warningIcon).toBeVisible(); | ||
|
|
||
| await warningIcon.hover(); | ||
| const tooltip = locators.filePicker.warningTooltip(); | ||
| await expect(tooltip).toBeVisible(); | ||
| await expect(tooltip).toContainText(MISSING_FILE_WARNING); | ||
|
Comment on lines
+42
to
+54
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 📐 Maintainability & Code Quality | 🟠 Major | ⚡ Quick win Move these selectors into the page locator module. This spec directly uses As per path instructions, specs must not inline raw selectors; selectors belong in 🤖 Prompt for AI AgentsSource: Path instructions |
||
| }); | ||
| }); | ||
| }); | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,41 @@ | ||
| opencollection: 1.0.0 | ||
| info: | ||
| name: Binary body type | ||
| config: | ||
| proxy: | ||
| inherit: true | ||
| config: | ||
| protocol: http | ||
| hostname: '' | ||
| port: '' | ||
| auth: | ||
| username: '' | ||
| password: '' | ||
| bypassProxy: '' | ||
| items: | ||
| - info: | ||
| name: Binary body - missing file | ||
| type: http | ||
| seq: 2 | ||
| http: | ||
| method: POST | ||
| url: https://api.com/users | ||
| body: | ||
| type: file | ||
| data: | ||
| - filePath: ./missing-payload.bin | ||
| contentType: '' | ||
| selected: true | ||
| auth: inherit | ||
| settings: | ||
| encodeUrl: true | ||
| timeout: 0 | ||
| followRedirects: true | ||
| maxRedirects: 5 | ||
| bundled: true | ||
| extensions: | ||
| bruno: | ||
| ignore: | ||
| - node_modules | ||
| - .git | ||
| exportedUsing: Bruno |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
Make the warning discoverable without hover.
The warning is attached to a plain, non-focusable SVG with a mouse-triggered tooltip, so keyboard and screen-reader users cannot reliably discover why the file is highlighted. Use a focusable semantic trigger or provide equivalent accessible text/ARIA.
Also applies to: 128-138
🤖 Prompt for AI Agents