@@ -7,6 +7,7 @@ import * as Sink from "effect/Sink";
77import * as Stream from "effect/Stream" ;
88import * as PlatformError from "effect/PlatformError" ;
99import { ChildProcessSpawner } from "effect/unstable/process" ;
10+ import { beforeEach , vi } from "vite-plus/test" ;
1011
1112import {
1213 ChromiumKeyError ,
@@ -17,6 +18,18 @@ import {
1718} from "./ChromiumKeys.ts" ;
1819import { LinuxBrowserSecretPath } from "./LinuxBrowserSecret.ts" ;
1920
21+ const { getPassword } = vi . hoisted ( ( ) => ( { getPassword : vi . fn < ( ) => string | null > ( ) } ) ) ;
22+
23+ vi . mock ( "@napi-rs/keyring" , ( ) => ( {
24+ Entry : class {
25+ getPassword = getPassword ;
26+ } ,
27+ } ) ) ;
28+
29+ beforeEach ( ( ) => {
30+ getPassword . mockReset ( ) ;
31+ } ) ;
32+
2033type CapturedCommand = {
2134 readonly command : string ;
2235 readonly args : ReadonlyArray < string > ;
@@ -63,6 +76,47 @@ const helperLayer = (input: {
6376 ) ,
6477 ) ;
6578
79+ describe ( "macOS Chromium secrets" , ( ) => {
80+ const request = {
81+ platform : "darwin" ,
82+ keychainService : "Chrome Safe Storage" ,
83+ keychainAccount : "Chrome" ,
84+ linuxSecretApplication : undefined ,
85+ } as const ;
86+ const noProcesses = Layer . succeed (
87+ ChildProcessSpawner . ChildProcessSpawner ,
88+ ChildProcessSpawner . make ( ( ) => Effect . die ( "must not spawn" ) ) ,
89+ ) ;
90+
91+ it . effect ( "derives the cookie key from the keychain secret" , ( ) =>
92+ Effect . gen ( function * ( ) {
93+ getPassword . mockReturnValue ( "macos-secret" ) ;
94+ const keys = yield * resolveChromiumKeys ( request ) ;
95+ expect ( keys . cbcV10 ?. toString ( "hex" ) ) . toBe ( "3df7306fb1eac353289565a2f6b64f74" ) ;
96+ } ) . pipe ( Effect . provide ( noProcesses ) ) ,
97+ ) ;
98+
99+ it . effect ( "reports a missing keychain entry" , ( ) =>
100+ Effect . gen ( function * ( ) {
101+ getPassword . mockReturnValue ( null ) ;
102+ const error = yield * resolveChromiumKeys ( request ) . pipe ( Effect . flip ) ;
103+ expect ( error . reason ) . toBe ( "keychainItemMissing" ) ;
104+ } ) . pipe ( Effect . provide ( noProcesses ) ) ,
105+ ) ;
106+
107+ it . effect ( "preserves a denied keychain approval" , ( ) =>
108+ Effect . gen ( function * ( ) {
109+ const denied = new Error ( "User denied access" ) ;
110+ getPassword . mockImplementation ( ( ) => {
111+ throw denied ;
112+ } ) ;
113+ const error = yield * resolveChromiumKeys ( request ) . pipe ( Effect . flip ) ;
114+ expect ( error . reason ) . toBe ( "needsKeychainApproval" ) ;
115+ expect ( error . cause ) . toBe ( denied ) ;
116+ } ) . pipe ( Effect . provide ( noProcesses ) ) ,
117+ ) ;
118+ } ) ;
119+
66120describe ( "Linux Chromium secrets" , ( ) => {
67121 it . effect ( "retains a missing helper failure alongside the keyring-free fallback" , ( ) =>
68122 Effect . gen ( function * ( ) {
0 commit comments