@@ -87,6 +87,224 @@ describe("cloudflareClient.listRecords", () => {
8787 } ) ;
8888} ) ;
8989
90+ describe ( "cloudflareClient MX priority" , ( ) => {
91+ it ( "inlines the priority into the content when listing" , async ( ) => {
92+ mockFetch . mockResolvedValue (
93+ cfSuccess ( [
94+ {
95+ id : "mx-1" ,
96+ type : "MX" ,
97+ name : "example.com" ,
98+ content : "mail.example.com" ,
99+ ttl : 300 ,
100+ priority : 20 ,
101+ } ,
102+ ] ) ,
103+ ) ;
104+
105+ const records = await cloudflareClient . listRecords ( config , "zone-1" ) ;
106+
107+ expect ( records [ 0 ] ?. content ) . toBe ( "20 mail.example.com" ) ;
108+ } ) ;
109+
110+ it ( "splits the priority back out when writing" , async ( ) => {
111+ mockFetch
112+ . mockResolvedValueOnce ( cfSuccess ( [ ] ) )
113+ . mockResolvedValueOnce ( cfSuccess ( { id : "mx-1" } ) ) ;
114+
115+ await cloudflareClient . upsertRecord ( config , {
116+ zoneId : "zone-1" ,
117+ type : "MX" ,
118+ name : "example.com" ,
119+ content : "20 mail.example.com" ,
120+ } ) ;
121+
122+ const [ , init ] = mockFetch . mock . calls [ 1 ] as [ string , RequestInit ] ;
123+ expect ( JSON . parse ( init . body as string ) ) . toMatchObject ( {
124+ content : "mail.example.com" ,
125+ priority : 20 ,
126+ } ) ;
127+ } ) ;
128+
129+ it ( "falls back to priority 10 when the content has no leading number" , async ( ) => {
130+ mockFetch
131+ . mockResolvedValueOnce ( cfSuccess ( [ ] ) )
132+ . mockResolvedValueOnce ( cfSuccess ( { id : "mx-1" } ) ) ;
133+
134+ await cloudflareClient . upsertRecord ( config , {
135+ zoneId : "zone-1" ,
136+ type : "MX" ,
137+ name : "example.com" ,
138+ content : "mail.example.com" ,
139+ } ) ;
140+
141+ const [ , init ] = mockFetch . mock . calls [ 1 ] as [ string , RequestInit ] ;
142+ expect ( JSON . parse ( init . body as string ) ) . toMatchObject ( {
143+ content : "mail.example.com" ,
144+ priority : 10 ,
145+ } ) ;
146+ } ) ;
147+
148+ it ( "leaves non-MX content untouched" , async ( ) => {
149+ mockFetch
150+ . mockResolvedValueOnce ( cfSuccess ( [ ] ) )
151+ . mockResolvedValueOnce ( cfSuccess ( { id : "txt-1" } ) ) ;
152+
153+ await cloudflareClient . upsertRecord ( config , {
154+ zoneId : "zone-1" ,
155+ type : "TXT" ,
156+ name : "example.com" ,
157+ content : "10 not-a-priority" ,
158+ } ) ;
159+
160+ const [ , init ] = mockFetch . mock . calls [ 1 ] as [ string , RequestInit ] ;
161+ const body = JSON . parse ( init . body as string ) ;
162+ expect ( body . content ) . toBe ( "10 not-a-priority" ) ;
163+ expect ( body . priority ) . toBeUndefined ( ) ;
164+ } ) ;
165+ } ) ;
166+
167+ describe ( "cloudflareClient structured records" , ( ) => {
168+ const stubCreate = ( ) =>
169+ mockFetch
170+ . mockResolvedValueOnce ( cfSuccess ( [ ] ) )
171+ . mockResolvedValueOnce ( cfSuccess ( { id : "rec-1" } ) ) ;
172+
173+ it ( "sends SRV values as structured data" , async ( ) => {
174+ stubCreate ( ) ;
175+
176+ await cloudflareClient . upsertRecord ( config , {
177+ zoneId : "zone-1" ,
178+ type : "SRV" ,
179+ name : "_sip._tcp.example.com" ,
180+ content : "1 10 5269 talk.example.com" ,
181+ } ) ;
182+
183+ const [ , init ] = mockFetch . mock . calls [ 1 ] as [ string , RequestInit ] ;
184+ const body = JSON . parse ( init . body as string ) ;
185+ expect ( body . data ) . toEqual ( {
186+ priority : 1 ,
187+ weight : 10 ,
188+ port : 5269 ,
189+ target : "talk.example.com" ,
190+ } ) ;
191+ expect ( body . content ) . toBeUndefined ( ) ;
192+ } ) ;
193+
194+ it ( "sends CAA values as structured data and drops the quotes" , async ( ) => {
195+ stubCreate ( ) ;
196+
197+ await cloudflareClient . upsertRecord ( config , {
198+ zoneId : "zone-1" ,
199+ type : "CAA" ,
200+ name : "example.com" ,
201+ content : '0 issue "letsencrypt.org"' ,
202+ } ) ;
203+
204+ const [ , init ] = mockFetch . mock . calls [ 1 ] as [ string , RequestInit ] ;
205+ const body = JSON . parse ( init . body as string ) ;
206+ expect ( body . data ) . toEqual ( {
207+ flags : 0 ,
208+ tag : "issue" ,
209+ value : "letsencrypt.org" ,
210+ } ) ;
211+ expect ( body . content ) . toBeUndefined ( ) ;
212+ } ) ;
213+
214+ it ( "rejects an SRV value that is missing a part" , async ( ) => {
215+ await expect (
216+ cloudflareClient . upsertRecord ( config , {
217+ zoneId : "zone-1" ,
218+ type : "SRV" ,
219+ name : "_sip._tcp.example.com" ,
220+ content : "1 10 5269" ,
221+ } ) ,
222+ ) . rejects . toThrow ( / S R V v a l u e / ) ;
223+ } ) ;
224+
225+ it ( "rejects a CAA value that has no tag" , async ( ) => {
226+ await expect (
227+ cloudflareClient . upsertRecord ( config , {
228+ zoneId : "zone-1" ,
229+ type : "CAA" ,
230+ name : "example.com" ,
231+ content : "0" ,
232+ } ) ,
233+ ) . rejects . toThrow ( / C A A v a l u e / ) ;
234+ } ) ;
235+ } ) ;
236+
237+ describe ( "cloudflareClient proxy status" , ( ) => {
238+ it ( "sends the proxy status for proxiable types" , async ( ) => {
239+ mockFetch
240+ . mockResolvedValueOnce ( cfSuccess ( [ ] ) )
241+ . mockResolvedValueOnce ( cfSuccess ( { id : "a-1" } ) ) ;
242+
243+ await cloudflareClient . upsertRecord ( config , {
244+ zoneId : "zone-1" ,
245+ type : "A" ,
246+ name : "app.example.com" ,
247+ content : "1.2.3.4" ,
248+ proxied : true ,
249+ } ) ;
250+
251+ const [ , init ] = mockFetch . mock . calls [ 1 ] as [ string , RequestInit ] ;
252+ expect ( JSON . parse ( init . body as string ) . proxied ) . toBe ( true ) ;
253+ } ) ;
254+
255+ it ( "omits the proxy status for types Cloudflare cannot proxy" , async ( ) => {
256+ mockFetch
257+ . mockResolvedValueOnce ( cfSuccess ( [ ] ) )
258+ . mockResolvedValueOnce ( cfSuccess ( { id : "txt-1" } ) ) ;
259+
260+ await cloudflareClient . upsertRecord ( config , {
261+ zoneId : "zone-1" ,
262+ type : "TXT" ,
263+ name : "example.com" ,
264+ content : "hello" ,
265+ proxied : true ,
266+ } ) ;
267+
268+ const [ , init ] = mockFetch . mock . calls [ 1 ] as [ string , RequestInit ] ;
269+ expect ( JSON . parse ( init . body as string ) . proxied ) . toBeUndefined ( ) ;
270+ } ) ;
271+
272+ it ( "leaves the proxy status untouched when the caller does not set it" , async ( ) => {
273+ mockFetch
274+ . mockResolvedValueOnce ( cfSuccess ( [ ] ) )
275+ . mockResolvedValueOnce ( cfSuccess ( { id : "a-1" } ) ) ;
276+
277+ await cloudflareClient . upsertRecord ( config , {
278+ zoneId : "zone-1" ,
279+ type : "A" ,
280+ name : "app.example.com" ,
281+ content : "1.2.3.4" ,
282+ } ) ;
283+
284+ const [ , init ] = mockFetch . mock . calls [ 1 ] as [ string , RequestInit ] ;
285+ expect ( "proxied" in JSON . parse ( init . body as string ) ) . toBe ( false ) ;
286+ } ) ;
287+
288+ it ( "returns the proxy status when listing records" , async ( ) => {
289+ mockFetch . mockResolvedValue (
290+ cfSuccess ( [
291+ {
292+ id : "a-1" ,
293+ type : "A" ,
294+ name : "app.example.com" ,
295+ content : "1.2.3.4" ,
296+ ttl : 1 ,
297+ proxied : true ,
298+ } ,
299+ ] ) ,
300+ ) ;
301+
302+ const records = await cloudflareClient . listRecords ( config , "zone-1" ) ;
303+
304+ expect ( records [ 0 ] ?. proxied ) . toBe ( true ) ;
305+ } ) ;
306+ } ) ;
307+
90308describe ( "cloudflareClient.upsertRecord" , ( ) => {
91309 it ( "creates a record when none exists for the name/type" , async ( ) => {
92310 mockFetch
0 commit comments