Skip to content

Commit 01f8957

Browse files
committed
fix: finalize policy-driven settings and preference sync
Signed-off-by: Vitor Mattos <[email protected]>
1 parent 892fbd0 commit 01f8957

11 files changed

Lines changed: 203 additions & 198 deletions

File tree

lib/Controller/AdminController.php

Lines changed: 0 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,6 @@
1616
use OCA\Libresign\Exception\LibresignException;
1717
use OCA\Libresign\Handler\CertificateEngine\CertificateEngineFactory;
1818
use OCA\Libresign\Handler\CertificateEngine\IEngineHandler;
19-
use OCA\Libresign\Helper\ConfigureCheckHelper;
2019
use OCA\Libresign\Service\Certificate\ValidateService;
2120
use OCA\Libresign\Service\CertificatePolicyService;
2221
use OCA\Libresign\Service\DocMdp\ConfigService as DocMdpConfigService;
@@ -238,12 +237,6 @@ public function loadCertificate(): DataResponse {
238237
$engine = $this->certificateEngineFactory->getEngine();
239238
/** @var LibresignEngineHandler */
240239
$certificate = $engine->toArray();
241-
$configureResult = $engine->configureCheck();
242-
$success = array_filter(
243-
$configureResult,
244-
fn (ConfigureCheckHelper $config) => $config->getStatus() === 'success'
245-
);
246-
$certificate['generated'] = count($success) === count($configureResult);
247240

248241
return new DataResponse($certificate);
249242
}

lib/Service/Policy/Runtime/PolicySource.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -179,7 +179,7 @@ public function loadUserPolicyConfig(string $policyKey, string $userId): ?Policy
179179
}
180180

181181
$decodedPayload = $this->deserializeStoredUserPolicyPayload($storedPayload);
182-
if (!is_array($decodedPayload) || !array_key_exists('value', $decodedPayload)) {
182+
if (!is_array($decodedPayload) || !array_key_exists('value', $decodedPayload) || $decodedPayload['value'] === null) {
183183
return null;
184184
}
185185

@@ -286,7 +286,7 @@ public function loadAllUserPolicies(array $policyKeys, PolicyContext $context):
286286

287287
$definition = $this->registry->get($policyKey);
288288
$decodedPayload = $this->deserializeStoredUserPolicyPayload($row['configvalue']);
289-
if (!is_array($decodedPayload) || !array_key_exists('value', $decodedPayload)) {
289+
if (!is_array($decodedPayload) || !array_key_exists('value', $decodedPayload) || $decodedPayload['value'] === null) {
290290
continue;
291291
}
292292

playwright/e2e/mobile-pdf-horizontal-scroll.spec.ts

Lines changed: 16 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@
55

66
import { devices, expect, test } from '@playwright/test'
77
import { login } from '../support/nc-login'
8-
import { setAppConfig } from '../support/nc-provisioning'
98

109
test.use({
1110
...devices['Pixel 7'],
@@ -18,17 +17,24 @@ test('PDF viewer allows horizontal scrolling on mobile viewport', async ({ page
1817
process.env.NEXTCLOUD_ADMIN_PASSWORD ?? 'admin',
1918
)
2019

21-
await setAppConfig(
22-
page.request,
23-
'libresign', 'add_footer', '1',
24-
)
20+
await page.goto('./settings/admin/libresign')
2521

26-
await setAppConfig(
27-
page.request,
28-
'libresign', 'footer_template_is_default', '0',
29-
)
22+
const addFooterSwitch = page.locator('.checkbox-radio-switch').filter({ hasText: /Add visible footer/i }).first()
23+
await expect(addFooterSwitch).toBeVisible({ timeout: 20000 })
24+
const addFooterCheckbox = addFooterSwitch.locator('input[type="checkbox"]').first()
25+
if (!await addFooterCheckbox.isChecked()) {
26+
await addFooterSwitch.click()
27+
await expect(addFooterCheckbox).toBeChecked()
28+
}
29+
30+
const customizeSwitch = page.locator('.checkbox-radio-switch').filter({ hasText: /Customize footer template/i }).first()
31+
await expect(customizeSwitch).toBeVisible({ timeout: 20000 })
32+
const customizeCheckbox = customizeSwitch.locator('input[type="checkbox"]').first()
33+
if (!await customizeCheckbox.isChecked()) {
34+
await customizeSwitch.click()
35+
await expect(customizeCheckbox).toBeChecked()
36+
}
3037

31-
await page.goto('./settings/admin/libresign')
3238
const pdfRoot = page.locator('.footer-template-section .pdf-elements-root').first()
3339
await expect(pdfRoot).toBeVisible({ timeout: 15000 })
3440

src/tests/views/Preferences/Preferences.spec.ts

Lines changed: 74 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -149,6 +149,7 @@ describe('Preferences view', () => {
149149

150150
it('renders signing order section without verbose summary labels', async () => {
151151
const wrapper = await createWrapper()
152+
await nextTick()
152153

153154
expect(wrapper.text()).toContain('Signing order')
154155
expect(wrapper.text()).not.toContain('Effective value')
@@ -337,6 +338,7 @@ describe('Preferences view', () => {
337338
})
338339

339340
const wrapper = await createWrapper()
341+
await nextTick()
340342

341343
expect(wrapper.text()).toContain('Signature footer')
342344
expect(wrapper.findComponent({ name: 'SignatureFooterRuleEditor' }).exists()).toBe(true)
@@ -374,7 +376,7 @@ describe('Preferences view', () => {
374376

375377
expect(saveUserPreferenceMock).toHaveBeenCalledWith(
376378
'add_footer',
377-
'{"enabled":true,"writeQrcodeOnFooter":true,"validationSite":"","customizeFooterTemplate":true,"footerTemplate":"Changed template"}',
379+
'{"enabled":true,"writeQrcodeOnFooter":true,"validationSite":"","customizeFooterTemplate":true,"footerTemplate":"Changed template","previewWidth":595,"previewHeight":100,"previewZoom":100}',
378380
)
379381
})
380382

@@ -388,6 +390,75 @@ describe('Preferences view', () => {
388390
expect(saveUserPreferenceMock).toHaveBeenCalledWith('signature_flow', 'ordered_numeric')
389391
})
390392

393+
it('ignores no-op preference updates emitted during editor hydration', async () => {
394+
const wrapper = await createWrapper()
395+
await nextTick()
396+
397+
saveUserPreferenceMock.mockClear()
398+
399+
await wrapper.vm.onPreferenceChange('signature_flow', 'parallel')
400+
401+
expect(saveUserPreferenceMock).not.toHaveBeenCalled()
402+
})
403+
404+
it('does not autosave preference updates before initialization finishes', async () => {
405+
let resolveFetchEffectivePolicies: (() => void) | null = null
406+
fetchEffectivePoliciesMock.mockImplementation(() => new Promise<void>((resolve) => {
407+
resolveFetchEffectivePolicies = resolve
408+
}))
409+
410+
const wrapper = await createWrapper()
411+
412+
expect(wrapper.vm.preferencesReady).toBe(false)
413+
414+
await wrapper.vm.onPreferenceChange('signature_flow', 'ordered_numeric')
415+
416+
expect(saveUserPreferenceMock).not.toHaveBeenCalled()
417+
418+
resolveFetchEffectivePolicies?.()
419+
await nextTick()
420+
await Promise.resolve()
421+
422+
expect(wrapper.vm.preferencesReady).toBe(true)
423+
424+
await wrapper.vm.onPreferenceChange('signature_flow', 'parallel')
425+
await wrapper.vm.onPreferenceChange('signature_flow', 'ordered_numeric')
426+
427+
expect(saveUserPreferenceMock).toHaveBeenCalledWith('signature_flow', 'ordered_numeric')
428+
})
429+
430+
it('ignores footer updates that only normalize to the current effective value', async () => {
431+
getPolicyMock.mockImplementation((key: string) => {
432+
if (key === 'add_footer') {
433+
return {
434+
policyKey: 'add_footer',
435+
effectiveValue: '{"enabled":true,"writeQrcodeOnFooter":true,"validationSite":"","customizeFooterTemplate":true,"footerTemplate":"Inherited footer template"}',
436+
sourceScope: 'group',
437+
visible: true,
438+
editableByCurrentActor: true,
439+
allowedValues: [],
440+
blockedBy: null,
441+
canSaveAsUserDefault: true,
442+
canUseAsRequestOverride: true,
443+
preferenceWasCleared: false,
444+
groupCount: 0,
445+
userCount: 0,
446+
}
447+
}
448+
449+
return null
450+
})
451+
452+
const wrapper = await createWrapper()
453+
await nextTick()
454+
455+
saveUserPreferenceMock.mockClear()
456+
457+
await wrapper.vm.onPreferenceChange('add_footer', '{"enabled":true,"writeQrcodeOnFooter":true,"validationSite":"","customizeFooterTemplate":true,"footerTemplate":"Inherited footer template","previewWidth":595,"previewHeight":100,"previewZoom":100}')
458+
459+
expect(saveUserPreferenceMock).not.toHaveBeenCalled()
460+
})
461+
391462
it('does not expose reset action after footer autosave when no persisted user default exists yet', async () => {
392463
getPolicyMock.mockImplementation((key: string) => {
393464
if (key === 'add_footer') {
@@ -419,7 +490,7 @@ describe('Preferences view', () => {
419490

420491
expect(saveUserPreferenceMock).toHaveBeenCalledWith(
421492
'add_footer',
422-
'{"enabled":true,"writeQrcodeOnFooter":true,"validationSite":"","customizeFooterTemplate":true,"footerTemplate":"Changed template"}',
493+
'{"enabled":true,"writeQrcodeOnFooter":true,"validationSite":"","customizeFooterTemplate":true,"footerTemplate":"Changed template","previewWidth":595,"previewHeight":100,"previewZoom":100}',
423494
)
424495
expect(wrapper.vm.isAutoSaveSavedFor('add_footer')).toBe(true)
425496
expect(wrapper.vm.canUndoAutoSaveFor('add_footer')).toBe(false)
@@ -470,6 +541,7 @@ describe('Preferences view', () => {
470541
})
471542

472543
const wrapper = await createWrapper()
544+
await nextTick()
473545

474546
expect(wrapper.text()).toContain('Signature footer')
475547
expect(wrapper.findComponent({ name: 'SignatureFooterRuleEditor' }).exists()).toBe(false)

src/tests/views/Settings/PolicyWorkbench/useSignatureTextPolicy.spec.ts

Lines changed: 14 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -37,21 +37,12 @@ describe('useSignatureTextPolicy', () => {
3737
})
3838

3939
it('reads canonical UI defaults without leaking effective render mode', () => {
40-
Object.assign(initialState, {
41-
default_signature_text_template: 'Default template',
42-
default_template_font_size: '11.5',
43-
default_signature_font_size: '14.5',
44-
default_signature_width: '120',
45-
default_signature_height: '80',
46-
signature_render_mode: 'DESCRIPTION_ONLY',
47-
})
48-
4940
expect(getSignatureTextUiDefaults()).toEqual({
50-
template: 'Default template',
51-
templateFontSize: 11.5,
52-
signatureFontSize: 14.5,
53-
signatureWidth: 120,
54-
signatureHeight: 80,
41+
template: '',
42+
templateFontSize: 9,
43+
signatureFontSize: 9,
44+
signatureWidth: 90,
45+
signatureHeight: 60,
5546
renderMode: 'GRAPHIC_AND_DESCRIPTION',
5647
})
5748
})
@@ -88,29 +79,23 @@ describe('useSignatureTextPolicy', () => {
8879
})
8980
})
9081

91-
it('falls back to legacy initial state values when no effective policy exists', () => {
82+
it('falls back to policy UI defaults when no effective policy exists', () => {
9283
Object.assign(initialState, {
93-
signature_text_template: 'Legacy template',
94-
template_font_size: '12.5',
95-
signature_font_size: '16.5',
96-
signature_width: '155',
97-
signature_height: '95',
98-
signature_render_mode: 'DESCRIPTION_ONLY',
9984
signature_text_template_error: '',
100-
signature_text_parsed: '<p>Legacy parsed</p>',
85+
signature_text_parsed: '<p>Parsed fallback</p>',
10186
})
10287

10388
const { values } = useSignatureTextPolicy()
10489

10590
expect(values.value).toEqual({
106-
template: 'Legacy template',
107-
templateFontSize: 12.5,
108-
signatureFontSize: 16.5,
109-
signatureWidth: 155,
110-
signatureHeight: 95,
111-
renderMode: 'DESCRIPTION_ONLY',
91+
template: '',
92+
templateFontSize: 9,
93+
signatureFontSize: 9,
94+
signatureWidth: 90,
95+
signatureHeight: 60,
96+
renderMode: 'GRAPHIC_AND_DESCRIPTION',
11297
templateError: '',
113-
parsed: '<p>Legacy parsed</p>',
98+
parsed: '<p>Parsed fallback</p>',
11499
})
115100
})
116101
})

src/tests/views/Settings/SignatureStamp.spec.ts

Lines changed: 26 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -44,18 +44,32 @@ vi.mock('@nextcloud/initial-state', () => ({
4444
loadState: vi.fn((_app: string, key: string, defaultValue?: unknown) => {
4545
const defaults: Record<string, unknown> = {
4646
signature_background_type: 'default',
47-
default_signature_text_template: 'Signed by {{ signerName }}',
48-
default_template_font_size: 10,
49-
default_signature_font_size: 18,
50-
default_signature_width: 180,
51-
default_signature_height: 90,
52-
signature_text_template: 'Signed by {{ signerName }}',
53-
signature_width: 180,
54-
signature_height: 90,
55-
signature_font_size: 18,
56-
template_font_size: 10,
47+
effective_policies: {
48+
policies: {
49+
signature_text: {
50+
policyKey: 'signature_text',
51+
effectiveValue: JSON.stringify({
52+
template: 'Signed by {{ signerName }}',
53+
template_font_size: 10,
54+
signature_font_size: 18,
55+
signature_width: 180,
56+
signature_height: 90,
57+
render_mode: 'GRAPHIC_AND_DESCRIPTION',
58+
}),
59+
sourceScope: 'system',
60+
visible: true,
61+
editableByCurrentActor: true,
62+
allowedValues: [],
63+
canSaveAsUserDefault: true,
64+
canUseAsRequestOverride: true,
65+
preferenceWasCleared: false,
66+
blockedBy: null,
67+
groupCount: 0,
68+
userCount: 0,
69+
},
70+
},
71+
},
5772
signature_preview_zoom_level: 100,
58-
signature_render_mode: 'GRAPHIC_AND_DESCRIPTION',
5973
signature_text_template_error: '',
6074
signature_text_parsed: '<p>Signed by Jane Doe</p>',
6175
signature_available_variables: {
@@ -227,11 +241,11 @@ describe('SignatureStamp.vue', () => {
227241
it('hides preview when background is deleted and description text is empty', () => {
228242
stateOverrides = {
229243
signature_background_type: 'deleted',
230-
signature_render_mode: 'DESCRIPTION_ONLY',
231244
signature_text_parsed: '',
232245
}
233246

234247
const wrapper = createWrapper()
248+
wrapper.vm.renderMode = 'DESCRIPTION_ONLY'
235249

236250
expect(wrapper.vm.displayPreview).toBe(false)
237251
})

0 commit comments

Comments
 (0)