@@ -55,3 +55,99 @@ describe('tool-call semaphore helpers', () => {
5555 assert . ok ( src . includes ( '_pendingToolQueue' ) , '_pendingToolQueue must be defined' ) ;
5656 } ) ;
5757} ) ;
58+
59+ import { AirtableClient } from '../src/client.js' ;
60+
61+ describe ( 'AirtableClient.deleteFields' , ( ) => {
62+ it ( 'processes all fields and reports succeeded/failed counts' , async ( ) => {
63+ // Schema contains all three fields so resolveField succeeds for each.
64+ // The cache is invalidated after each successful delete, causing a re-fetch
65+ // that always returns the full schema. postForm fails only for fldBAD.
66+ const SCHEMA = {
67+ data : {
68+ tableSchemas : [ {
69+ id : 'tblAAA' ,
70+ columns : [
71+ { id : 'fld001' , name : 'Field A' , type : 'text' , typeOptions : { } } ,
72+ { id : 'fldBAD' , name : 'Bad Field' , type : 'text' , typeOptions : { } } ,
73+ { id : 'fld003' , name : 'Field C' , type : 'text' , typeOptions : { } } ,
74+ ] ,
75+ views : [ ] ,
76+ } ] ,
77+ } ,
78+ } ;
79+ const auth = createMockAuth ( {
80+ get ( ) {
81+ return { ok : true , status : 200 , json : async ( ) => SCHEMA , text : async ( ) => '{}' } ;
82+ } ,
83+ postForm ( url ) {
84+ // fldBAD destroy call fails with a non-dependency error → throws in deleteField
85+ if ( url . includes ( 'fldBAD' ) ) {
86+ return { ok : false , status : 422 , json : async ( ) => ( { error : { type : 'NOT_FOUND' } } ) , text : async ( ) => 'not found' } ;
87+ }
88+ return { ok : true , status : 200 , json : async ( ) => ( { } ) , text : async ( ) => '{}' } ;
89+ } ,
90+ } ) ;
91+ const client = new AirtableClient ( auth ) ;
92+ const fields = [
93+ { fieldId : 'fld001' , expectedName : 'Field A' } ,
94+ { fieldId : 'fldBAD' , expectedName : 'Bad Field' } ,
95+ { fieldId : 'fld003' , expectedName : 'Field C' } ,
96+ ] ;
97+ const result = await client . deleteFields ( 'appTEST' , fields , { force : true } ) ;
98+ assert . equal ( result . succeeded . length , 2 , 'two fields should succeed' ) ;
99+ assert . equal ( result . failed . length , 1 , 'one field should fail' ) ;
100+ assert . equal ( result . failed [ 0 ] . fieldId , 'fldBAD' ) ;
101+ assert . ok ( typeof result . failed [ 0 ] . error === 'string' , 'error must be a string' ) ;
102+ } ) ;
103+
104+ it ( 'calls onProgress once per field' , async ( ) => {
105+ const auth = createMockAuth ( {
106+ get ( ) {
107+ return {
108+ ok : true , status : 200 ,
109+ json : async ( ) => ( {
110+ data : { tableSchemas : [ { id : 'tbl1' , columns : [ { id : 'fld001' , name : 'A' , type : 'text' , typeOptions : { } } ] , views : [ ] } ] } ,
111+ } ) ,
112+ text : async ( ) => '{}' ,
113+ } ;
114+ } ,
115+ } ) ;
116+ const client = new AirtableClient ( auth ) ;
117+ const progressLog = [ ] ;
118+ await client . deleteFields ( 'appTEST' , [ { fieldId : 'fld001' , expectedName : 'A' } ] , {
119+ onProgress : ( info ) => progressLog . push ( info ) ,
120+ } ) ;
121+ assert . equal ( progressLog . length , 1 ) ;
122+ assert . equal ( progressLog [ 0 ] . index , 0 ) ;
123+ assert . equal ( progressLog [ 0 ] . total , 1 ) ;
124+ } ) ;
125+
126+ it ( 'continues processing after a per-field failure' , async ( ) => {
127+ // Schema contains both fields. fld001 postForm fails (non-dep error → throws),
128+ // fld002 succeeds. Cache is invalidated only on success, but the schema mock
129+ // always returns both fields so both resolveField calls succeed regardless.
130+ const SCHEMA2 = {
131+ data : { tableSchemas : [ { id : 'tbl1' , columns : [
132+ { id : 'fld001' , name : 'A' , type : 'text' , typeOptions : { } } ,
133+ { id : 'fld002' , name : 'B' , type : 'text' , typeOptions : { } } ,
134+ ] , views : [ ] } ] } ,
135+ } ;
136+ const auth = createMockAuth ( {
137+ get ( ) {
138+ return { ok : true , status : 200 , json : async ( ) => SCHEMA2 , text : async ( ) => '{}' } ;
139+ } ,
140+ postForm ( url ) {
141+ if ( url . includes ( 'fld001' ) ) return { ok : false , status : 422 , json : async ( ) => ( { error : { type : 'ERR' } } ) , text : async ( ) => 'err' } ;
142+ return { ok : true , status : 200 , json : async ( ) => ( { } ) , text : async ( ) => '{}' } ;
143+ } ,
144+ } ) ;
145+ const client = new AirtableClient ( auth ) ;
146+ const result = await client . deleteFields ( 'appTEST' , [
147+ { fieldId : 'fld001' , expectedName : 'A' } ,
148+ { fieldId : 'fld002' , expectedName : 'B' } ,
149+ ] , { force : true } ) ;
150+ assert . equal ( result . succeeded . length , 1 ) ;
151+ assert . equal ( result . failed . length , 1 ) ;
152+ } ) ;
153+ } ) ;
0 commit comments