Skip to content

Commit a0595d4

Browse files
test: add init.ts regressions for files menu and sidebar flow
Signed-off-by: Vitor Mattos <1079143+vitormattos@users.noreply.github.com>
1 parent b2c5d6f commit a0595d4

1 file changed

Lines changed: 73 additions & 4 deletions

File tree

src/tests/init.spec.ts

Lines changed: 73 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -17,10 +17,22 @@ import { describe, expect, it, beforeEach, vi, afterEach } from 'vitest'
1717
* The action files were never imported by any bundle entry point, so
1818
* registerFileAction() was never called for them.
1919
* Fix: init.ts now imports both action modules as side-effects.
20+
*
21+
* Bug 3: POST /request-signature missing "file" parameter after upload.
22+
* client.stat() was called WITHOUT getDefaultPropfind() data, so the WebDAV
23+
* PROPFIND response omitted the Nextcloud-specific {owncloud}fileid property.
24+
* resultToNode() produced a Node with fileid = undefined, mapNodeToFileInfo
25+
* returned id = '', addFile() silently rejected the temp record, selectedFileId
26+
* stayed 0, and saveOrUpdateSignatureRequest sent POST without any file reference.
27+
* Fix: pass data: getDefaultPropfind() to client.stat() so fileid is always
28+
* included and the sidebar can correctly identify the uploaded file.
2029
*/
2130

2231
// ─── Mocks ────────────────────────────────────────────────────────────────────
2332

33+
const mockDefaultPropfind = '<propfind xmlns="DAV:"><prop><fileid/></prop></propfind>'
34+
const mockGetDefaultPropfind = vi.fn(() => mockDefaultPropfind)
35+
2436
const mockStat = vi.fn()
2537
const mockClient = { stat: mockStat }
2638
const mockGetClient = vi.fn(() => mockClient)
@@ -41,6 +53,11 @@ const mockGetUploader = vi.fn(() => mockUploader)
4153

4254
const mockAxiosPost = vi.fn(() => Promise.resolve({ data: {} }))
4355

56+
const mockLoadState = vi.fn((app: string, key: string, defaultValue?: unknown) => {
57+
if (app === 'libresign' && key === 'certificate_ok') return true
58+
return defaultValue
59+
})
60+
4461
// ─── Module-level mocks (hoisted before imports) ─────────────────────────────
4562

4663
vi.mock('@nextcloud/axios', () => ({
@@ -66,8 +83,13 @@ vi.mock('@nextcloud/files', () => ({
6683
registerFileAction: mockRegisterFileAction,
6784
}))
6885

86+
vi.mock('@nextcloud/initial-state', () => ({
87+
loadState: mockLoadState,
88+
}))
89+
6990
vi.mock('@nextcloud/files/dav', () => ({
7091
getClient: mockGetClient,
92+
getDefaultPropfind: mockGetDefaultPropfind,
7193
getRootPath: mockGetRootPath,
7294
resultToNode: mockResultToNode,
7395
registerDavProperty: mockRegisterDavProperty,
@@ -98,7 +120,8 @@ vi.mock('../actions/showStatusInlineAction.js', () => ({}))
98120
*/
99121
function captureNewMenuHandler(): (context: unknown, content: unknown) => Promise<void> {
100122
expect(mockAddNewFileMenuEntry).toHaveBeenCalledOnce()
101-
const [[entry]] = mockAddNewFileMenuEntry.mock.calls as [[{ handler: (context: unknown, content: unknown) => Promise<void>; uploadManager?: { upload: typeof mockUpload } }]]
123+
type MenuEntry = { handler: (context: unknown, content: unknown) => Promise<void>; uploadManager?: { upload: typeof mockUpload } }
124+
const entry = mockAddNewFileMenuEntry.mock.calls[0][0] as MenuEntry
102125
// Inject the mock uploader so handler can call this.uploadManager.upload()
103126
entry.uploadManager = mockUploader
104127
return entry.handler.bind(entry)
@@ -172,10 +195,41 @@ describe('init.ts', () => {
172195

173196
it('adds a "New signature request" entry to the Files new-menu', () => {
174197
expect(mockAddNewFileMenuEntry).toHaveBeenCalledOnce()
175-
const [entry] = mockAddNewFileMenuEntry.mock.calls[0] as [{ id: string }][]
198+
const entry = mockAddNewFileMenuEntry.mock.calls[0][0] as { id: string }
176199
expect(entry.id).toBe('libresign-request')
177200
})
178201

202+
/**
203+
* Regression: sidebar did not open on LibreSign tab.
204+
* The menu entry must be hidden when LibreSign's certificate is not
205+
* configured (certificate_ok = false), because isEnabled() in tab.ts also
206+
* checks certificate_ok and rejects unconfigured instances — causing the
207+
* sidebar to fall back to the default (Details) tab.
208+
*/
209+
describe('menu entry enabled() guard', () => {
210+
type MenuEntry = {
211+
enabled: (context: { permissions: number }) => boolean
212+
}
213+
214+
it('is enabled when certificate_ok is true and folder has CREATE permission', () => {
215+
mockLoadState.mockReturnValue(true)
216+
const entry = mockAddNewFileMenuEntry.mock.calls[0][0] as MenuEntry
217+
expect(entry.enabled({ permissions: 4 /* CREATE */ })).toBe(true)
218+
})
219+
220+
it('is disabled when certificate_ok is false (LibreSign not configured)', () => {
221+
mockLoadState.mockReturnValue(false)
222+
const entry = mockAddNewFileMenuEntry.mock.calls[0][0] as MenuEntry
223+
expect(entry.enabled({ permissions: 4 /* CREATE */ })).toBe(false)
224+
})
225+
226+
it('is disabled when folder lacks CREATE permission even with certificate_ok', () => {
227+
mockLoadState.mockReturnValue(true)
228+
const entry = mockAddNewFileMenuEntry.mock.calls[0][0] as MenuEntry
229+
expect(entry.enabled({ permissions: 0 })).toBe(false)
230+
})
231+
})
232+
179233
// ── Side-effect: file-action imports ─────────────────────────────────────
180234

181235
/**
@@ -213,13 +267,28 @@ describe('init.ts', () => {
213267
*/
214268
it('calls client.stat with getRootPath() prefix to avoid PROPFIND 404', () => {
215269
const expectedPath = `${mockGetRootPath()}${folderPath}/${fileName}`
216-
expect(mockStat).toHaveBeenCalledWith(expectedPath, { details: true })
270+
expect(mockStat).toHaveBeenCalledWith(expectedPath, expect.objectContaining({ details: true }))
217271
})
218272

219273
it('does NOT call client.stat with a bare path missing the root prefix', () => {
220274
const barePath = `${folderPath}/${fileName}`
221275
// Ensure the old (broken) path was never used
222-
expect(mockStat).not.toHaveBeenCalledWith(barePath, { details: true })
276+
expect(mockStat).not.toHaveBeenCalledWith(barePath, expect.anything())
277+
})
278+
279+
/**
280+
* Regression: POST /request-signature missing "file" parameter.
281+
* Without getDefaultPropfind(), the WebDAV PROPFIND response omits the
282+
* Nextcloud-specific fileid property. resultToNode() then returns a Node
283+
* with fileid = undefined, which propagates as an empty fileInfo.id through
284+
* the sidebar → addFile silently rejects the record → selectedFileId = 0 →
285+
* saveOrUpdateSignatureRequest sends POST with no file reference → 422.
286+
*/
287+
it('calls client.stat with getDefaultPropfind() data so fileid is returned', () => {
288+
expect(mockStat).toHaveBeenCalledWith(
289+
expect.any(String),
290+
expect.objectContaining({ data: mockDefaultPropfind }),
291+
)
223292
})
224293

225294
it('uploads the file before posting the OCS request', () => {

0 commit comments

Comments
 (0)