From 737c4ef4d0edb602b35785a5a394686401111b0e Mon Sep 17 00:00:00 2001 From: BatchData API Date: Wed, 11 Nov 2026 09:52:13 -0700 Subject: [PATCH] Make v0.3.1: port Zapier review fixes (connection label, 402/404, safer billed defaults) - Connection metadata label is "Token ending XXXX", with fallbacks "API token" / "API token missing". Make shows the app name beside the connection, so the label never includes "BatchData". - HTTP 402 maps to a clear insufficient-funds DataError; 404 says the ID or path was not found. - Billed searches (Search Properties, Get Investor Properties, Find Investors (Buy Box), Get Property Permits) default Limit to 1, because later modules run once per result. - Watch New Properties in a Search Session (Billed) defaults to 5 properties per check. - README notes the new defaults. Tests cover the label, error mapping and defaults. The shared catalog.json is unchanged. Batch row "Your Reference ID" already reaches the API in this app, so that Zapier fix did not apply. --- README.md | 2 +- .../batchdata.communication.iml.json | 4 +++ general/base.iml.json | 8 +++++ .../getPermits.communication.iml.json | 8 ++--- .../getPermits.mappable-params.iml.json | 4 +-- .../investorBuybox.communication.iml.json | 8 ++--- .../investorBuybox.mappable-params.iml.json | 4 +-- .../investorProperties.communication.iml.json | 8 ++--- ...nvestorProperties.mappable-params.iml.json | 4 +-- .../searchProperties.communication.iml.json | 8 ++--- .../searchProperties.mappable-params.iml.json | 4 +-- ...chSessionProperties.static-params.iml.json | 4 +-- package.json | 2 +- scripts/catalog-modules.mjs | 16 ++++++---- scripts/generate.mjs | 9 +++++- scripts/params.mjs | 2 +- test/catalog.test.mjs | 2 +- test/pagination.test.mjs | 2 +- test/replay.test.mjs | 4 +-- test/structure.test.mjs | 32 +++++++++++++++++++ 20 files changed, 95 insertions(+), 40 deletions(-) diff --git a/README.md b/README.md index aba7b99..7fcfab7 100644 --- a/README.md +++ b/README.md @@ -100,7 +100,7 @@ A monitor reports when a property is added to it, removed from it or changed. Search Properties, Get Investor Properties, Find Investors (Buy Box), Get Property Permits, List Search Sessions and Watch New Search Sessions have a **Limit** field. It is the total number of records you want. Make fetches them in pages of up to 100 and never asks for more than your Limit, so the Limit is also the most you can be billed for in one run. Search Properties and List Search Sessions also have **Skip** to start part-way through the results. -Each record comes out as its own bundle. +Each record comes out as its own bundle. On billed searches (Search Properties, Get Investor Properties, Find Investors (Buy Box) and Get Property Permits) the default Limit is 1, so a test run does not bill a full page. Raise it when you need more records. Watch New Properties in a Search Session (Billed) defaults to 5 properties per check. ## Billing diff --git a/connections/batchdata/batchdata.communication.iml.json b/connections/batchdata/batchdata.communication.iml.json index 30347b4..7aaa2a3 100644 --- a/connections/batchdata/batchdata.communication.iml.json +++ b/connections/batchdata/batchdata.communication.iml.json @@ -13,6 +13,10 @@ "data": { "apiKey": "{{parameters.apiKey}}" }, + "metadata": { + "type": "text", + "value": "{{if(length(parameters.apiKey) >= 8, 'Token ending ' + substring(parameters.apiKey, length(parameters.apiKey) - 4), if(length(parameters.apiKey) > 0, 'API token', 'API token missing'))}}" + }, "error": { "401": { "type": "InvalidAccessTokenError", diff --git a/general/base.iml.json b/general/base.iml.json index 7e647f4..fd0b43b 100644 --- a/general/base.iml.json +++ b/general/base.iml.json @@ -14,10 +14,18 @@ "type": "InvalidAccessTokenError", "message": "Your BatchData API token is invalid or expired. Create a new connection with a valid token." }, + "402": { + "type": "DataError", + "message": "{{ifempty(body.status.message, 'Your BatchData account balance is too low for this request.')}} Add funds in your BatchData account, then retry. Contact support@batchservice.com for help." + }, "403": { "type": "RuntimeError", "message": "Your BatchData API token does not have access to this data. Contact support@batchservice.com to enable it." }, + "404": { + "type": "RuntimeError", + "message": "[404] {{ifempty(body.status.message, 'Not found. Check the ID or path.')}}" + }, "429": { "type": "RateLimitError", "message": "The BatchData rate limit was reached. Wait a moment and try again." diff --git a/modules/getPermits/getPermits.communication.iml.json b/modules/getPermits/getPermits.communication.iml.json index e1a628a..e447e6c 100644 --- a/modules/getPermits/getPermits.communication.iml.json +++ b/modules/getPermits/getPermits.communication.iml.json @@ -15,7 +15,7 @@ "countyFipsCode": "{{ifempty(parameters.countyFipsCode, undefined)}}" }, "options": { - "take": "{{min(100, ifempty(parameters.limit, 25))}}" + "take": "{{min(100, ifempty(parameters.limit, 1))}}" } }, "response": { @@ -25,7 +25,7 @@ "property": "{{body.result.property}}", "searchMeta": "{{body.result.meta.results}}" }, - "limit": "{{ifempty(parameters.limit, 25)}}" + "limit": "{{ifempty(parameters.limit, 1)}}" }, "pagination": { "body": { @@ -42,10 +42,10 @@ "countyFipsCode": "{{ifempty(parameters.countyFipsCode, undefined)}}" }, "options": { - "take": "{{min(100, ifempty(parameters.limit, 25) - (pagination.page - 1) * 100)}}", + "take": "{{min(100, ifempty(parameters.limit, 1) - (pagination.page - 1) * 100)}}", "skip": "{{0 + (pagination.page - 1) * 100}}" } }, - "condition": "{{body.result.meta.results.resultsFound > 0 + (pagination.page - 1) * 100 && ifempty(parameters.limit, 25) > (pagination.page - 1) * 100}}" + "condition": "{{body.result.meta.results.resultsFound > 0 + (pagination.page - 1) * 100 && ifempty(parameters.limit, 1) > (pagination.page - 1) * 100}}" } } diff --git a/modules/getPermits/getPermits.mappable-params.iml.json b/modules/getPermits/getPermits.mappable-params.iml.json index 54cff46..84142e9 100644 --- a/modules/getPermits/getPermits.mappable-params.iml.json +++ b/modules/getPermits/getPermits.mappable-params.iml.json @@ -251,12 +251,12 @@ "name": "limit", "type": "uinteger", "label": "Limit", - "default": 25, + "default": 1, "required": true, "validate": { "min": 1, "max": 5000 }, - "help": "The maximum number of permits to return in total (1 to 5000, default 25). They are requested in pages of up to 100, and never more than this number are requested. Each page is billed per property, so a Limit above 100 sends more than one billed request." + "help": "The maximum number of permits to return in total (1 to 5000, default 1). They are requested in pages of up to 100, and never more than this number are requested. Later modules run once per permit. Each page is billed per property, so a Limit above 100 sends more than one billed request." } ] diff --git a/modules/investorBuybox/investorBuybox.communication.iml.json b/modules/investorBuybox/investorBuybox.communication.iml.json index 7a07935..94f90f9 100644 --- a/modules/investorBuybox/investorBuybox.communication.iml.json +++ b/modules/investorBuybox/investorBuybox.communication.iml.json @@ -14,7 +14,7 @@ } }, "options": { - "take": "{{min(100, ifempty(parameters.limit, 25))}}" + "take": "{{min(100, ifempty(parameters.limit, 1))}}" } }, "response": { @@ -24,7 +24,7 @@ "searchMeta": "{{body.result.meta.results}}", "warnings": "{{body.result.warnings}}" }, - "limit": "{{ifempty(parameters.limit, 25)}}" + "limit": "{{ifempty(parameters.limit, 1)}}" }, "pagination": { "body": { @@ -40,10 +40,10 @@ } }, "options": { - "take": "{{min(100, ifempty(parameters.limit, 25) - (pagination.page - 1) * 100)}}", + "take": "{{min(100, ifempty(parameters.limit, 1) - (pagination.page - 1) * 100)}}", "skip": "{{0 + (pagination.page - 1) * 100}}" } }, - "condition": "{{body.result.meta.results.resultsFound > 0 + (pagination.page - 1) * 100 && ifempty(parameters.limit, 25) > (pagination.page - 1) * 100}}" + "condition": "{{body.result.meta.results.resultsFound > 0 + (pagination.page - 1) * 100 && ifempty(parameters.limit, 1) > (pagination.page - 1) * 100}}" } } diff --git a/modules/investorBuybox/investorBuybox.mappable-params.iml.json b/modules/investorBuybox/investorBuybox.mappable-params.iml.json index 547183e..1ae7632 100644 --- a/modules/investorBuybox/investorBuybox.mappable-params.iml.json +++ b/modules/investorBuybox/investorBuybox.mappable-params.iml.json @@ -99,12 +99,12 @@ "name": "limit", "type": "uinteger", "label": "Limit", - "default": 25, + "default": 1, "required": true, "validate": { "min": 1, "max": 5000 }, - "help": "The maximum number of investors to return in total (1 to 5000, default 25). They are requested in pages of up to 100, and never more than this number are requested." + "help": "The maximum number of investors to return in total (1 to 5000, default 1). They are requested in pages of up to 100, and never more than this number are requested. Later modules run once per investor. You are billed per record returned." } ] diff --git a/modules/investorProperties/investorProperties.communication.iml.json b/modules/investorProperties/investorProperties.communication.iml.json index 7ee3416..a283193 100644 --- a/modules/investorProperties/investorProperties.communication.iml.json +++ b/modules/investorProperties/investorProperties.communication.iml.json @@ -8,7 +8,7 @@ "includeChildEntities": "{{if(parameters.includeChildEntities == true, true, undefined)}}" }, "options": { - "take": "{{min(100, ifempty(parameters.limit, 25))}}", + "take": "{{min(100, ifempty(parameters.limit, 1))}}", "pageCursor": "{{ifempty(parameters.pageCursor, undefined)}}" } }, @@ -19,7 +19,7 @@ "resultsFound": "{{body.result.resultsFound}}", "nextPageCursor": "{{body.result.nextPageCursor}}" }, - "limit": "{{ifempty(parameters.limit, 25)}}" + "limit": "{{ifempty(parameters.limit, 1)}}" }, "pagination": { "body": { @@ -29,10 +29,10 @@ "includeChildEntities": "{{if(parameters.includeChildEntities == true, true, undefined)}}" }, "options": { - "take": "{{min(100, ifempty(parameters.limit, 25) - (pagination.page - 1) * 100)}}", + "take": "{{min(100, ifempty(parameters.limit, 1) - (pagination.page - 1) * 100)}}", "pageCursor": "{{body.result.nextPageCursor}}" } }, - "condition": "{{length(ifempty(body.result.nextPageCursor, '')) > 0 && ifempty(parameters.limit, 25) > (pagination.page - 1) * 100}}" + "condition": "{{length(ifempty(body.result.nextPageCursor, '')) > 0 && ifempty(parameters.limit, 1) > (pagination.page - 1) * 100}}" } } diff --git a/modules/investorProperties/investorProperties.mappable-params.iml.json b/modules/investorProperties/investorProperties.mappable-params.iml.json index c8834a2..c44d7e9 100644 --- a/modules/investorProperties/investorProperties.mappable-params.iml.json +++ b/modules/investorProperties/investorProperties.mappable-params.iml.json @@ -34,13 +34,13 @@ "name": "limit", "type": "uinteger", "label": "Limit", - "default": 25, + "default": 1, "required": true, "validate": { "min": 1, "max": 5000 }, - "help": "The maximum number of properties to return in total (1 to 5000, default 25). They are requested in pages of up to 100, and never more than this number are requested." + "help": "The maximum number of properties to return in total (1 to 5000, default 1). They are requested in pages of up to 100, and never more than this number are requested. Later modules run once per property. You are billed per record returned." }, { "name": "pageCursor", diff --git a/modules/searchProperties/searchProperties.communication.iml.json b/modules/searchProperties/searchProperties.communication.iml.json index df307f0..327b385 100644 --- a/modules/searchProperties/searchProperties.communication.iml.json +++ b/modules/searchProperties/searchProperties.communication.iml.json @@ -5,7 +5,7 @@ "searchCriteria": "{{omit(parameters, 'limit', 'skip')}}", "options": { "skip": "{{ifempty(parameters.skip, undefined)}}", - "take": "{{min(100, ifempty(parameters.limit, 25))}}" + "take": "{{min(100, ifempty(parameters.limit, 1))}}" } }, "response": { @@ -14,16 +14,16 @@ "{{...}}": "{{item}}", "searchMeta": "{{body.results.meta.results}}" }, - "limit": "{{ifempty(parameters.limit, 25)}}" + "limit": "{{ifempty(parameters.limit, 1)}}" }, "pagination": { "body": { "searchCriteria": "{{omit(parameters, 'limit', 'skip')}}", "options": { "skip": "{{ifempty(parameters.skip, 0) + (pagination.page - 1) * 100}}", - "take": "{{min(100, ifempty(parameters.limit, 25) - (pagination.page - 1) * 100)}}" + "take": "{{min(100, ifempty(parameters.limit, 1) - (pagination.page - 1) * 100)}}" } }, - "condition": "{{body.results.meta.results.resultsFound > ifempty(parameters.skip, 0) + (pagination.page - 1) * 100 && ifempty(parameters.limit, 25) > (pagination.page - 1) * 100}}" + "condition": "{{body.results.meta.results.resultsFound > ifempty(parameters.skip, 0) + (pagination.page - 1) * 100 && ifempty(parameters.limit, 1) > (pagination.page - 1) * 100}}" } } diff --git a/modules/searchProperties/searchProperties.mappable-params.iml.json b/modules/searchProperties/searchProperties.mappable-params.iml.json index 5c9339f..fd41059 100644 --- a/modules/searchProperties/searchProperties.mappable-params.iml.json +++ b/modules/searchProperties/searchProperties.mappable-params.iml.json @@ -701,13 +701,13 @@ "name": "limit", "type": "uinteger", "label": "Limit", - "default": 25, + "default": 1, "required": true, "validate": { "min": 1, "max": 5000 }, - "help": "The maximum number of properties to return in total (1 to 5000, default 25). They are requested in pages of up to 100, and never more than this number are requested. You are billed per record returned." + "help": "The maximum number of properties to return in total (1 to 5000, default 1). They are requested in pages of up to 100, and never more than this number are requested. You are billed per record returned. Later modules run once per property, so keep Limit at 1 unless you need more." }, { "name": "skip", diff --git a/modules/watchSessionProperties/watchSessionProperties.static-params.iml.json b/modules/watchSessionProperties/watchSessionProperties.static-params.iml.json index d9cc1de..4967f92 100644 --- a/modules/watchSessionProperties/watchSessionProperties.static-params.iml.json +++ b/modules/watchSessionProperties/watchSessionProperties.static-params.iml.json @@ -350,9 +350,9 @@ { "name": "take", "label": "Max Results", - "help": "Whole number from 1 to 100. Default 25. You are billed per property delivered on each check.", + "help": "Whole number from 1 to 100. Default 5. You are billed per property delivered on each check. Use a low number while you test.", "type": "integer", - "default": 25, + "default": 5, "validate": { "min": 1, "max": 100 diff --git a/package.json b/package.json index c5c4a68..f664c3b 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "batchdata-make", - "version": "0.3.0", + "version": "0.3.1", "private": true, "description": "BatchData custom app for Make", "type": "module", diff --git a/scripts/catalog-modules.mjs b/scripts/catalog-modules.mjs index c00078d..8e2b7e2 100644 --- a/scripts/catalog-modules.mjs +++ b/scripts/catalog-modules.mjs @@ -323,6 +323,10 @@ const trig = (key) => catalog.triggers.find((t) => t.key === key); staticParams: staticFromFields(t), sampleFrom: () => ({ ...t.sample, searchMeta: { totalCount: 1 } }), }; + const take = catalogModules.watchSessionProperties.staticParams.find((x) => x.name === 'take'); + if (!take) throw new Error('watchSessionProperties has no take parameter'); + take.default = 5; + take.help = 'Whole number from 1 to 100. Default 5. You are billed per property delivered on each check. Use a low number while you test.'; } // ---------- instant trigger: webhook component + two modules ---------- @@ -401,19 +405,19 @@ const swapTakeForLimit = (m, param) => { }; { const m = catalogModules.getPermits; - swapTakeForLimit(m, limitParam(25, { what: 'permits', extra: ' Each page is billed per property, so a Limit above 100 sends more than one billed request.' })); - offsetPaging(m.communication, { def: 25, found: 'body.result.meta.results.resultsFound', where: 'body' }); + swapTakeForLimit(m, limitParam(1, { what: 'permits', extra: ' Later modules run once per permit. Each page is billed per property, so a Limit above 100 sends more than one billed request.' })); + offsetPaging(m.communication, { def: 1, found: 'body.result.meta.results.resultsFound', where: 'body' }); } { const m = catalogModules.investorBuybox; - swapTakeForLimit(m, limitParam(25, { what: 'investors' })); - offsetPaging(m.communication, { def: 25, found: 'body.result.meta.results.resultsFound', where: 'body' }); + swapTakeForLimit(m, limitParam(1, { what: 'investors', extra: ' Later modules run once per investor. You are billed per record returned.' })); + offsetPaging(m.communication, { def: 1, found: 'body.result.meta.results.resultsFound', where: 'body' }); } { // Cursor paging only (skip is rejected). A full last page still returns a cursor, and the request // that follows it is empty and not billed. const m = catalogModules.investorProperties; - swapTakeForLimit(m, limitParam(25, { what: 'properties' })); + swapTakeForLimit(m, limitParam(1, { what: 'properties', extra: ' Later modules run once per property. You are billed per record returned.' })); m.description = m.description .replace('Use the "nextPageCursor" value of the last result as Page Cursor to read the next page.', 'Set a Limit and Make reads the pages for you; each result also carries the "nextPageCursor" of its page.') .replace('(take sets the maximum cost of a call)', '(the Limit sets the maximum cost of a run)'); @@ -421,7 +425,7 @@ const swapTakeForLimit = (m, param) => { const cur = m.params.find((x) => x.name === 'pageCursor'); cur.label = 'Start from page cursor'; cur.help = 'Optional. Leave empty to start at the first page. The Limit already pages for you, so use this only to continue an earlier run from its Next Page Cursor.'; - cursorPaging(m.communication, { def: 25, cursor: 'body.result.nextPageCursor' }); + cursorPaging(m.communication, { def: 1, cursor: 'body.result.nextPageCursor' }); } { const m = catalogModules.listSearchSessions; diff --git a/scripts/generate.mjs b/scripts/generate.mjs index ef37b5d..063b8e9 100644 --- a/scripts/generate.mjs +++ b/scripts/generate.mjs @@ -195,7 +195,7 @@ const modules = { }, }; -offsetPaging(modules.searchProperties.communication, { def: 25, found: 'body.results.meta.results.resultsFound', where: 'body', skip: 'skip' }); +offsetPaging(modules.searchProperties.communication, { def: 1, found: 'body.results.meta.results.resultsFound', where: 'body', skip: 'skip' }); Object.assign(modules, catalogModules); const baseGroups = [ @@ -293,7 +293,9 @@ write('general/base.iml.json', { type: 'RuntimeError', 400: { type: 'DataError', message: "[400] {{ifempty(body.status.message, 'The request was rejected. Check the values you entered.')}}" }, 401: { type: 'InvalidAccessTokenError', message: 'Your BatchData API token is invalid or expired. Create a new connection with a valid token.' }, + 402: { type: 'DataError', message: "{{ifempty(body.status.message, 'Your BatchData account balance is too low for this request.')}} Add funds in your BatchData account, then retry. Contact support@batchservice.com for help." }, 403: { type: 'RuntimeError', message: 'Your BatchData API token does not have access to this data. Contact support@batchservice.com to enable it.' }, + 404: { type: 'RuntimeError', message: "[404] {{ifempty(body.status.message, 'Not found. Check the ID or path.')}}" }, 429: { type: 'RateLimitError', message: 'The BatchData rate limit was reached. Wait a moment and try again.' }, }, }, @@ -317,6 +319,11 @@ write('connections/batchdata/batchdata.communication.iml.json', { response: { valid: '{{statusCode == 400 || (statusCode >= 200 && statusCode < 300)}}', data: { apiKey: '{{parameters.apiKey}}' }, + // Make shows the app name beside every connection, so the label never repeats "BatchData". + metadata: { + type: 'text', + value: "{{if(length(parameters.apiKey) >= 8, 'Token ending ' + substring(parameters.apiKey, length(parameters.apiKey) - 4), if(length(parameters.apiKey) > 0, 'API token', 'API token missing'))}}", + }, error: { message: "[{{statusCode}}] {{ifempty(body.status.message, 'Could not verify the API token.')}}", 401: { type: 'InvalidAccessTokenError', message: 'Invalid API token. Copy a fresh token from your BatchData account.' }, diff --git a/scripts/params.mjs b/scripts/params.mjs index 31e20a3..893c315 100644 --- a/scripts/params.mjs +++ b/scripts/params.mjs @@ -90,7 +90,7 @@ export const searchParams = [ }, ], }, - limitParam(25, { what: 'properties', extra: ' You are billed per record returned.' }), + limitParam(1, { what: 'properties', extra: ' You are billed per record returned. Later modules run once per property, so keep Limit at 1 unless you need more.' }), { name: 'skip', type: 'uinteger', label: 'Skip', help: 'Number of records to skip before the first one returned. Leave empty to start at the first record.' }, ]; diff --git a/test/catalog.test.mjs b/test/catalog.test.mjs index 6309325..e404f28 100644 --- a/test/catalog.test.mjs +++ b/test/catalog.test.mjs @@ -154,7 +154,7 @@ test('permits, geocoding and investors build the documented requests', () => { const b = request('investorBuybox', { street: '1 Main St', investor: { classification: { inList: ['flipper'] } }, additionalCriteria: '{"x":{"y":1}}' }); assert.deepEqual(b.body.searchCriteria, { x: { y: 1 }, compAddress: { street: '1 Main St' }, investor: { classification: { inList: ['flipper'] } } }); const ip = request('investorProperties', { investorId: 'inv1', includeChildEntities: true }); - assert.deepEqual(ip.body, { searchCriteria: { investorId: 'inv1', includeChildEntities: true }, options: { take: 25 } }); + assert.deepEqual(ip.body, { searchCriteria: { investorId: 'inv1', includeChildEntities: true }, options: { take: 1 } }); }); test('search sessions: the session ID and cursor pagination are sent, deletes sanitize the ID', () => { diff --git a/test/pagination.test.mjs b/test/pagination.test.mjs index d7dff12..62290dd 100644 --- a/test/pagination.test.mjs +++ b/test/pagination.test.mjs @@ -174,7 +174,7 @@ for (const name of PAGED) { test(`${name}: the default Limit is a single request`, () => { const r = run(name, { ...params }, 1000); - const def = name.includes('Sessions') ? (name === 'watchSearchSessions' ? 100 : 50) : 25; + const def = { searchProperties: 1, investorProperties: 1, investorBuybox: 1, getPermits: 1, listSearchSessions: 50, watchSearchSessions: 100 }[name]; assert.equal(r.items.length, Math.min(def, 1000)); assert.equal(r.requests.length, 1); }); diff --git a/test/replay.test.mjs b/test/replay.test.mjs index bcdb75b..7b6fa10 100644 --- a/test/replay.test.mjs +++ b/test/replay.test.mjs @@ -19,7 +19,7 @@ const respond = (name, body, extra = {}) => { }; const iterate = (name, body) => render(comm(name).response.iterate, { body }); -test('search: only filled filters are sent, the Limit defaults to 25', () => { +test('search: only filled filters are sent, the Limit defaults to 1', () => { const r = request('searchProperties', { query: 'Phoenix, AZ', quickLists: ['absentee-owner', 'not-vacant'], @@ -34,7 +34,7 @@ test('search: only filled filters are sent, the Limit defaults to 25', () => { building: { bedroomCount: { min: 3 } }, owner: { ownerOccupied: { equals: false } }, }, - options: { take: 25 }, + options: { take: 1 }, }); }); diff --git a/test/structure.test.mjs b/test/structure.test.mjs index de4a837..5e0f4a7 100644 --- a/test/structure.test.mjs +++ b/test/structure.test.mjs @@ -167,3 +167,35 @@ test('the README is written for users', () => { test('no module description refers to itself', () => { for (const [key, m] of Object.entries(app.components.module)) assert.ok(!m.description.includes(`"${m.label}"`), `${key} mentions itself`); }); + +test('the connection label fallback never includes the app name', () => { + const comm = read('connections/batchdata/batchdata.communication.iml.json'); + const meta = comm.response.metadata; + assert.equal(meta.type, 'text'); + assert.match(meta.value, /Token ending/); + assert.match(meta.value, /API token/); + assert.match(meta.value, /API token missing/); + assert.ok(!/BatchData/i.test(meta.value), 'connection label must not include the app name'); +}); + +test('HTTP 402 and 404 have clear error messages', () => { + const errors = read('general/base.iml.json').response.error; + assert.equal(errors[402].type, 'DataError'); + assert.match(errors[402].message, /balance is too low/); + assert.match(errors[402].message, /support@batchservice\.com/); + assert.equal(errors[404].type, 'RuntimeError'); + assert.match(errors[404].message, /Not found/); +}); + +test('billed search modules default to Limit 1, and the billed session trigger defaults to 5', () => { + const app = read('makecomapp.json'); + for (const key of ['searchProperties', 'getPermits', 'investorBuybox', 'investorProperties']) { + const params = read(app.components.module[key].codeFiles.mappableParams); + const limit = params.find((p) => p.name === 'limit'); + assert.equal(limit.default, 1, key); + } + const take = read(app.components.module.watchSessionProperties.codeFiles.staticParams).find((p) => p.name === 'take'); + assert.equal(take.default, 5); + assert.match(take.help, /Default 5/); +}); +