@@ -76,13 +76,13 @@ describe('SeaAuth — edge cases (input validation + ambiguity)', () => {
7676 }
7777 } ) ;
7878
79- // Post round-3 NF-N3: a blank/reserved-literal `oauthClientSecret`
80- // routes the connection to the U2M arm rather than rejecting on
81- // the M2M arm. When `oauthClientId` is ALSO set, the U2M arm's
82- // dedicated "not supported on U2M" rejection fires — which is more
83- // actionable than the M2M "secret must be non-empty" message
84- // because it tells the user the U2M flow exists and how to use it .
85- it ( 'routes mixed-case reserved-literal oauthClientSecret to U2M; rejects with U2M- id error ' , ( ) => {
79+ // Round-4 NF3-2: presence of `oauthClientId` signals M2M intent.
80+ // A blank/reserved-literal `oauthClientSecret` is then a missing-secret
81+ // typo, not a request to fall back to U2M. Surface the M2M "secret
82+ // required" AuthenticationError so the user fixes the real problem
83+ // rather than swap class to a HiveDriverError pointing at a flow
84+ // they didn't intend to use.
85+ it ( 'rejects mixed-case reserved-literal oauthClientSecret with AuthenticationError when id is set ' , ( ) => {
8686 const opts : ConnectionOptions = {
8787 host : 'example.cloud.databricks.com' ,
8888 path : '/sql/1.0/warehouses/abc' ,
@@ -92,8 +92,8 @@ describe('SeaAuth — edge cases (input validation + ambiguity)', () => {
9292 } ;
9393
9494 expect ( ( ) => buildSeaConnectionOptions ( opts ) ) . to . throw (
95- HiveDriverError ,
96- / o a u t h C l i e n t I d . * n o t s u p p o r t e d o n t h e O A u t h U 2 M f l o w / ,
95+ AuthenticationError ,
96+ / o a u t h C l i e n t S e c r e t . * n o n - e m p t y . * O A u t h M 2 M / ,
9797 ) ;
9898 } ) ;
9999
@@ -112,7 +112,7 @@ describe('SeaAuth — edge cases (input validation + ambiguity)', () => {
112112 ) ;
113113 } ) ;
114114
115- it ( 'routes whitespace-only oauthClientSecret to U2M; with oauthClientId set, rejects U2M+id ' , ( ) => {
115+ it ( 'rejects whitespace-only oauthClientSecret with AuthenticationError when oauthClientId is set (M2M intent) ' , ( ) => {
116116 const opts : ConnectionOptions = {
117117 host : 'example.cloud.databricks.com' ,
118118 path : '/sql/1.0/warehouses/abc' ,
@@ -122,8 +122,8 @@ describe('SeaAuth — edge cases (input validation + ambiguity)', () => {
122122 } ;
123123
124124 expect ( ( ) => buildSeaConnectionOptions ( opts ) ) . to . throw (
125- HiveDriverError ,
126- / o a u t h C l i e n t I d . * n o t s u p p o r t e d o n t h e O A u t h U 2 M f l o w / ,
125+ AuthenticationError ,
126+ / o a u t h C l i e n t S e c r e t . * n o n - e m p t y . * O A u t h M 2 M / ,
127127 ) ;
128128 } ) ;
129129
@@ -142,7 +142,7 @@ describe('SeaAuth — edge cases (input validation + ambiguity)', () => {
142142 ) ;
143143 } ) ;
144144
145- it ( 'routes literal "undefined" as oauthClientSecret to U2M; with oauthClientId set, rejects U2M+id ' , ( ) => {
145+ it ( 'rejects literal "undefined" as oauthClientSecret with AuthenticationError when id is set (M2M intent) ' , ( ) => {
146146 const opts : ConnectionOptions = {
147147 host : 'example.cloud.databricks.com' ,
148148 path : '/sql/1.0/warehouses/abc' ,
@@ -152,10 +152,37 @@ describe('SeaAuth — edge cases (input validation + ambiguity)', () => {
152152 } ;
153153
154154 expect ( ( ) => buildSeaConnectionOptions ( opts ) ) . to . throw (
155- HiveDriverError ,
156- / o a u t h C l i e n t I d . * n o t s u p p o r t e d o n t h e O A u t h U 2 M f l o w / ,
155+ AuthenticationError ,
156+ / o a u t h C l i e n t S e c r e t . * n o n - e m p t y . * O A u t h M 2 M / ,
157157 ) ;
158158 } ) ;
159+
160+ // Round-4 NF3-2: pin the exact class — must be `AuthenticationError`,
161+ // not the bare `HiveDriverError` superclass. The round-3 NF-N3 fix
162+ // swapped this silently by routing M2M-with-empty-secret through the
163+ // U2M arm, which raised a plain `HiveDriverError`. Guard against that
164+ // regression by pinning the constructor name (since
165+ // `AuthenticationError extends HiveDriverError`, `instanceof` alone
166+ // can't distinguish the two).
167+ it ( 'M2M-with-empty-secret throws AuthenticationError, not bare HiveDriverError (class pin)' , ( ) => {
168+ const opts : ConnectionOptions = {
169+ host : 'example.cloud.databricks.com' ,
170+ path : '/sql/1.0/warehouses/abc' ,
171+ authType : 'databricks-oauth' ,
172+ oauthClientId : 'x' ,
173+ oauthClientSecret : '' ,
174+ } ;
175+
176+ let caught : unknown ;
177+ try {
178+ buildSeaConnectionOptions ( opts ) ;
179+ } catch ( e ) {
180+ caught = e ;
181+ }
182+ expect ( caught ) . to . be . instanceOf ( AuthenticationError ) ;
183+ expect ( ( caught as Error ) . constructor . name ) . to . equal ( 'AuthenticationError' ) ;
184+ expect ( ( caught as Error ) . message ) . to . match ( / o a u t h C l i e n t S e c r e t .* n o n - e m p t y .* O A u t h M 2 M / ) ;
185+ } ) ;
159186 } ) ;
160187
161188 describe ( 'ambiguous credentials are rejected' , ( ) => {
@@ -399,7 +426,7 @@ describe('SeaBackend — kernel error envelope decoding (DA-F1)', () => {
399426 expect ( ( caught as Error ) . message ) . to . match ( / ` t o k e n ` i s r e q u i r e d / ) ;
400427 } ) ;
401428
402- it ( 'falls back to original Error for a corrupted envelope' , async ( ) => {
429+ it ( 'falls back to original Error for a corrupted envelope, stripping the internal sentinel ' , async ( ) => {
403430 const binding = bindingRejectingWith ( 'not valid json' ) ;
404431 const backend = new SeaBackend ( binding ) ;
405432 await backend . connect ( validConnectArgs ) ;
@@ -414,6 +441,11 @@ describe('SeaBackend — kernel error envelope decoding (DA-F1)', () => {
414441 // the original Error so the operator sees the raw payload.
415442 expect ( caught ) . to . be . instanceOf ( Error ) ;
416443 expect ( ( caught as Error ) . message ) . to . contain ( 'not valid json' ) ;
444+ // Round-4 NF3-3: the `__databricks_error__:` prefix is an internal
445+ // JS<->binding framing marker; it must not leak to the user-facing
446+ // message even on the corrupted-envelope fallback path.
447+ expect ( ( caught as Error ) . message ) . to . not . match ( / ^ _ _ d a t a b r i c k s _ e r r o r _ _ : / ) ;
448+ expect ( ( caught as Error ) . message ) . to . equal ( 'not valid json' ) ;
417449 } ) ;
418450
419451 // NF-4 / NF-N1: preserve the 5 optional kernel envelope fields on the
0 commit comments