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 Verified
CD88F9E564E0102646FB6077A1A07C7C7C3E349D· 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 ( | |||
| 7 | 7 | ) | |
| 8 | 8 | ||
| 9 | 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 | 12 | type PermissionRepository interface { | |
| 12 | 13 | // Get retrieves the permission record for a specific origin and permission type. | |
| 13 | 14 | // 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) { | |||
| 533 | 533 | ||
| 534 | 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 | + | } | |
| @@ -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 | + | } | |||