detached fcdcea4

feat(permission): add website_data_access use case tests and update repo comment

commit
fcdcea4287c829ffbeaf5e39e57561dd24ae8883
author
brice · · 2026-03-17 19:10 UTC
parent
a1e4979
signature
GPG VerifiedCD88F9E564E0102646FB6077A1A07C7C7C3E349D · arch-gpg Verified for the committer email
2 files · +73 -1
M internal/application/port/permission_repository.go +2 -1
@@ -7,7 +7,8 @@ import (
77 )
88
99 // PermissionRepository defines operations for permission persistence.
10-// Only microphone and camera permissions are persisted per W3C spec.
10+// Persistable types include microphone, camera, website_data_access,
11+// and others where CanPersist() returns true.
1112 type PermissionRepository interface {
1213 // Get retrieves the permission record for a specific origin and permission type.
1314 // Returns nil if no record exists (treat as "prompt" state).
@@ -7,7 +7,8 @@ import (
7 )7 )
8 8
9 // PermissionRepository defines operations for permission persistence.9 // PermissionRepository defines operations for permission persistence.
10-// Only microphone and camera permissions are persisted per W3C spec.10+// Persistable types include microphone, camera, website_data_access,
11+// and others where CanPersist() returns true.
11 type PermissionRepository interface {12 type PermissionRepository interface {
12 // Get retrieves the permission record for a specific origin and permission type.13 // Get retrieves the permission record for a specific origin and permission type.
13 // Returns nil if no record exists (treat as "prompt" state).14 // Returns nil if no record exists (treat as "prompt" state).
M internal/application/usecase/handle_permission_test.go +71 -0
@@ -533,3 +533,74 @@ func TestHandlePermissionUseCase_UsesInjectedLoggerFactory(t *testing.T) {
533533
534534 assert.True(t, called)
535535 }
536+
537+func TestHandlePermissionUseCase_WebsiteDataAccess_ShowsDialogAndPersists(t *testing.T) {
538+ ctx := testContext()
539+ permRepo := portmocks.NewMockPermissionRepository(t)
540+ dialog := portmocks.NewMockPermissionDialogPresenter(t)
541+
542+ uc := usecase.NewHandlePermissionUseCase(permRepo, dialog, permissionLoggerFromContext)
543+
544+ // No stored permission
545+ permRepo.EXPECT().Get(mock.Anything, "https://accounts.google.com", entity.PermissionTypeWebsiteDataAccess).
546+ Return(nil, nil)
547+
548+ allowed := false
549+ callback := usecase.PermissionCallback{
550+ Allow: func() { allowed = true },
551+ Deny: func() {},
552+ }
553+
554+ // Dialog shows and user clicks "Always Allow"
555+ dialog.EXPECT().ShowPermissionDialog(
556+ mock.Anything,
557+ "https://accounts.google.com",
558+ []entity.PermissionType{entity.PermissionTypeWebsiteDataAccess},
559+ mock.Anything,
560+ ).Run(func(_ context.Context, _ string, _ []entity.PermissionType, cb func(port.PermissionDialogResult)) {
561+ cb(port.PermissionDialogResult{Allowed: true, Persistent: true})
562+ })
563+
564+ // Should persist the granted permission
565+ permRepo.EXPECT().Set(mock.Anything, mock.AnythingOfType("*entity.PermissionRecord")).
566+ Run(func(_ context.Context, r *entity.PermissionRecord) {
567+ assert.Equal(t, "https://accounts.google.com", r.Origin)
568+ assert.Equal(t, entity.PermissionTypeWebsiteDataAccess, r.Type)
569+ assert.Equal(t, entity.PermissionGranted, r.Decision)
570+ }).Return(nil)
571+
572+ uc.HandlePermissionRequest(ctx, "https://accounts.google.com", []entity.PermissionType{
573+ entity.PermissionTypeWebsiteDataAccess,
574+ }, callback)
575+
576+ assert.True(t, allowed)
577+}
578+
579+func TestHandlePermissionUseCase_WebsiteDataAccess_UsesStoredGrant(t *testing.T) {
580+ ctx := testContext()
581+ permRepo := portmocks.NewMockPermissionRepository(t)
582+ dialog := portmocks.NewMockPermissionDialogPresenter(t)
583+
584+ uc := usecase.NewHandlePermissionUseCase(permRepo, dialog, permissionLoggerFromContext)
585+
586+ // Stored granted permission
587+ permRepo.EXPECT().Get(mock.Anything, "https://accounts.google.com", entity.PermissionTypeWebsiteDataAccess).
588+ Return(&entity.PermissionRecord{
589+ Origin: "https://accounts.google.com",
590+ Type: entity.PermissionTypeWebsiteDataAccess,
591+ Decision: entity.PermissionGranted,
592+ }, nil)
593+
594+ allowed := false
595+ callback := usecase.PermissionCallback{
596+ Allow: func() { allowed = true },
597+ Deny: func() {},
598+ }
599+
600+ uc.HandlePermissionRequest(ctx, "https://accounts.google.com", []entity.PermissionType{
601+ entity.PermissionTypeWebsiteDataAccess,
602+ }, callback)
603+
604+ assert.True(t, allowed, "should use stored granted permission without dialog")
605+ dialog.AssertNotCalled(t, "ShowPermissionDialog")
606+}
@@ -533,3 +533,74 @@ func TestHandlePermissionUseCase_UsesInjectedLoggerFactory(t *testing.T) {
533 533
534 assert.True(t, called)534 assert.True(t, called)
535 }535 }
536+
537+func TestHandlePermissionUseCase_WebsiteDataAccess_ShowsDialogAndPersists(t *testing.T) {
538+ ctx := testContext()
539+ permRepo := portmocks.NewMockPermissionRepository(t)
540+ dialog := portmocks.NewMockPermissionDialogPresenter(t)
541+
542+ uc := usecase.NewHandlePermissionUseCase(permRepo, dialog, permissionLoggerFromContext)
543+
544+ // No stored permission
545+ permRepo.EXPECT().Get(mock.Anything, "https://accounts.google.com", entity.PermissionTypeWebsiteDataAccess).
546+ Return(nil, nil)
547+
548+ allowed := false
549+ callback := usecase.PermissionCallback{
550+ Allow: func() { allowed = true },
551+ Deny: func() {},
552+ }
553+
554+ // Dialog shows and user clicks "Always Allow"
555+ dialog.EXPECT().ShowPermissionDialog(
556+ mock.Anything,
557+ "https://accounts.google.com",
558+ []entity.PermissionType{entity.PermissionTypeWebsiteDataAccess},
559+ mock.Anything,
560+ ).Run(func(_ context.Context, _ string, _ []entity.PermissionType, cb func(port.PermissionDialogResult)) {
561+ cb(port.PermissionDialogResult{Allowed: true, Persistent: true})
562+ })
563+
564+ // Should persist the granted permission
565+ permRepo.EXPECT().Set(mock.Anything, mock.AnythingOfType("*entity.PermissionRecord")).
566+ Run(func(_ context.Context, r *entity.PermissionRecord) {
567+ assert.Equal(t, "https://accounts.google.com", r.Origin)
568+ assert.Equal(t, entity.PermissionTypeWebsiteDataAccess, r.Type)
569+ assert.Equal(t, entity.PermissionGranted, r.Decision)
570+ }).Return(nil)
571+
572+ uc.HandlePermissionRequest(ctx, "https://accounts.google.com", []entity.PermissionType{
573+ entity.PermissionTypeWebsiteDataAccess,
574+ }, callback)
575+
576+ assert.True(t, allowed)
577+}
578+
579+func TestHandlePermissionUseCase_WebsiteDataAccess_UsesStoredGrant(t *testing.T) {
580+ ctx := testContext()
581+ permRepo := portmocks.NewMockPermissionRepository(t)
582+ dialog := portmocks.NewMockPermissionDialogPresenter(t)
583+
584+ uc := usecase.NewHandlePermissionUseCase(permRepo, dialog, permissionLoggerFromContext)
585+
586+ // Stored granted permission
587+ permRepo.EXPECT().Get(mock.Anything, "https://accounts.google.com", entity.PermissionTypeWebsiteDataAccess).
588+ Return(&entity.PermissionRecord{
589+ Origin: "https://accounts.google.com",
590+ Type: entity.PermissionTypeWebsiteDataAccess,
591+ Decision: entity.PermissionGranted,
592+ }, nil)
593+
594+ allowed := false
595+ callback := usecase.PermissionCallback{
596+ Allow: func() { allowed = true },
597+ Deny: func() {},
598+ }
599+
600+ uc.HandlePermissionRequest(ctx, "https://accounts.google.com", []entity.PermissionType{
601+ entity.PermissionTypeWebsiteDataAccess,
602+ }, callback)
603+
604+ assert.True(t, allowed, "should use stored granted permission without dialog")
605+ dialog.AssertNotCalled(t, "ShowPermissionDialog")
606+}

Keyboard shortcuts

Single-key shortcuts are on. ctrl k and esc work whatever this says.

ctrl k :
go to a section, branch, tag, file or repository
?
this list
j k
move the row cursor down / up
enter
open the row under the cursor; on Files, a directory opens or closes in place
↑ ↓ ← → home end
in the file tree (focus on a row): previous / next row, close / open a directory or go to its parent / first entry, first / last row. They never open a file
l h
on Files: open a directory or a file / close a directory or go up to its parent, then to the parent page. Elsewhere: into the side panel (on a merge request, its files) / back to the content; there j k move between its controls
g g G
first / last row
/
filter the list; on Files, find a file anywhere in the repository (esc clears, enter opens the best match)
b
switch branch or tag
g s g f g c g b g m g i g r g a
tabs: summary, files, commits, branches & tags, merge requests, issues, releases, actions (when available)
g h
home
w
wide mode: use the whole width of the screen, or not (remembered)
y
copy a permalink pinned to the commit
d u x
diff: split / unified / collapse or expand all
e E
diff: the whole file in view / every file, with the lines the hunks hide
n
merge request: next note in the diff
esc
close a dialog, leave a field

Branches 8

Tags 42

↑↓ moveenter openesc close