diff --git a/.github/workflows/node-tests.yml b/.github/workflows/node-tests.yml index 53476e5..fdafee4 100644 --- a/.github/workflows/node-tests.yml +++ b/.github/workflows/node-tests.yml @@ -16,9 +16,9 @@ jobs: node-version: [20.x, 22.x, 24.x] steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v6 - name: Use Node.js ${{ matrix.node-version }} - uses: actions/setup-node@v1 + uses: actions/setup-node@v6 with: node-version: ${{ matrix.node-version }} - run: npm ci diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml new file mode 100644 index 0000000..ce16469 --- /dev/null +++ b/.github/workflows/publish.yml @@ -0,0 +1,39 @@ +name: Publish to npm + +on: + push: + tags: + - '*' + +jobs: + publish: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v6 + + - name: Setup Node.js + uses: actions/setup-node@v6 + with: + node-version: '20.x' + registry-url: 'https://registry.npmjs.org' + + - name: Install dependencies + run: npm ci + + - name: Run tests + run: npm test + + - name: Build + run: npm run build + + - name: Set version from tag + run: | + VERSION=${GITHUB_REF#refs/tags/} + sed -i 's/"version": "0\.0\.0"/"version": "'"$VERSION"'"/g' package.json + sed -i 's/"version": "0\.0\.0"/"version": "'"$VERSION"'"/g' package-lock.json + + - name: Publish to npm + run: npm publish + env: + NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} diff --git a/Makefile b/Makefile index 6904171..496ea68 100644 --- a/Makefile +++ b/Makefile @@ -1,31 +1,45 @@ #!/usr/bin/make -f -VERSION := $(shell tagit -p --dry-run) -VERSION_FILE1 := package.json -VERSION_FILE2 := package-lock.json +VERSION_FILE1 := package.json +VERSION_FILE2 := package-lock.json -test: node_modules +test: node_modules fmt npm run test node_modules: npm install -build: +fmt: + npx prettier --write . + +clean: + @git checkout "$(VERSION_FILE1)" "$(VERSION_FILE2)" + +build: node_modules npm run build -publish: test version build upload unversion - tagit -p - git push origin --tags +compile: build -upload: - npm publish +cover: node_modules + npm run test -- --reporter=spec + +integrate: build + @echo "Running integration examples..." + @node examples/us_street.mjs > /dev/null || true + @node examples/us_zipcode.mjs > /dev/null || true + @node examples/us_autocomplete_pro.mjs > /dev/null || true + @node examples/us_extract.mjs > /dev/null || true + @node examples/us_reverse_geo.mjs > /dev/null || true + @node examples/us_enrichment.mjs > /dev/null || true + @node examples/international_street.mjs > /dev/null || true + @node examples/international_address_autocomplete.mjs > /dev/null || true + @node examples/international_postal_code.mjs > /dev/null || true version: sed -i.bak -e 's/^ "version": "0\.0\.0",/ "version": "$(VERSION)",/g' "$(VERSION_FILE1)" && rm -f "$(VERSION_FILE1).bak" sed -i.bak -e 's/^ "version": "0\.0\.0",/ "version": "$(VERSION)",/g' "$(VERSION_FILE2)" && rm -f "$(VERSION_FILE2).bak" -unversion: - git checkout "$(VERSION_FILE1)" "$(VERSION_FILE2)" +publish: test build version + npm publish -# node_modules is a real directory target -.PHONY: test publish upload version unversion +.PHONY: test fmt clean build compile cover integrate version publish diff --git a/src/us_street/Client.js b/src/us_street/Client.js index 3449278..e9ab0e5 100644 --- a/src/us_street/Client.js +++ b/src/us_street/Client.js @@ -3,7 +3,7 @@ const Lookup = require("./Lookup"); const Batch = require("../Batch"); const UndefinedLookupError = require("../Errors").UndefinedLookupError; const sendBatch = require("../util/sendBatch"); -const keyTranslationFormat = require("../util/apiToSDKKeyMap").usStreet; +const buildUsStreetInputData = require("../util/buildUsStreetInputData"); /** * This client sends lookups to the Smarty US Street API,
@@ -28,15 +28,13 @@ class Client { let batch; if (dataIsLookup) { - if (data.maxCandidates == null && data.match == "enhanced") - data.maxCandidates = 5; batch = new Batch(); batch.add(data); } else { batch = data; } - return sendBatch(batch, this.sender, Candidate, keyTranslationFormat); + return sendBatch(batch, this.sender, Candidate, null, buildUsStreetInputData); } } diff --git a/src/util/apiToSDKKeyMap.js b/src/util/apiToSDKKeyMap.js index ea947c5..6201311 100644 --- a/src/util/apiToSDKKeyMap.js +++ b/src/util/apiToSDKKeyMap.js @@ -12,7 +12,8 @@ module.exports = { "match": "match", "format": "format", "candidates": "maxCandidates", - "county_source": "countySource" + "county_source": "countySource", + "input_id": "inputId" }, usAutocompletePro: { search: "search", diff --git a/src/util/buildUsStreetInputData.js b/src/util/buildUsStreetInputData.js new file mode 100644 index 0000000..a122803 --- /dev/null +++ b/src/util/buildUsStreetInputData.js @@ -0,0 +1,31 @@ +const buildInputData = require("./buildInputData"); +const keyTranslationFormat = require("./apiToSDKKeyMap").usStreet; + +module.exports = (lookup) => { + // Apply default match strategy and candidates logic per Go SDK behavior + let effectiveMatch = lookup.match; + let effectiveCandidates = lookup.maxCandidates; + + // Default match strategy is "enhanced" + if (!effectiveMatch) { + effectiveMatch = "enhanced"; + } + + // If match is "strict", don't send match parameter + if (effectiveMatch === "strict") { + effectiveMatch = undefined; + } + + // For "enhanced" match mode, set default candidates to 5 if not specified + if (effectiveMatch === "enhanced" && !effectiveCandidates) { + effectiveCandidates = 5; + } + + // Create a lookup copy with effective values for serialization + const effectiveLookup = Object.assign({}, lookup, { + match: effectiveMatch, + maxCandidates: effectiveCandidates, + }); + + return buildInputData(effectiveLookup, keyTranslationFormat); +}; diff --git a/src/util/sendBatch.js b/src/util/sendBatch.js index eca89a1..a32ab63 100644 --- a/src/util/sendBatch.js +++ b/src/util/sendBatch.js @@ -2,7 +2,7 @@ const Request = require("../Request"); const Errors = require("../Errors"); const buildInputData = require("../util/buildInputData"); -module.exports = (batch, sender, Result, keyTranslationFormat) => { +module.exports = (batch, sender, Result, keyTranslationFormat, customBuildInputData) => { if (batch.isEmpty()) throw new Errors.BatchEmptyError; let request = new Request(); @@ -22,6 +22,9 @@ module.exports = (batch, sender, Result, keyTranslationFormat) => { function generateRequestPayload(batch) { return batch.lookups.map((lookup) => { + if (customBuildInputData) { + return customBuildInputData(lookup); + } return buildInputData(lookup, keyTranslationFormat); }); } diff --git a/tests/us_street/test_Client.js b/tests/us_street/test_Client.js index 4864845..703000c 100644 --- a/tests/us_street/test_Client.js +++ b/tests/us_street/test_Client.js @@ -42,6 +42,23 @@ describe("A US Street client", function () { urbanization: "9", match: "10", candidates: "11", + input_id: "12", + }; + + client.send(lookup); + + expect(mockSender.request.parameters).to.deep.equal(expectedParameters); + }); + + it("defaults to enhanced match with 5 candidates when no match type specified.", function () { + let mockSender = new MockSender(); + const client = new Client(mockSender); + let lookup = new Lookup(); + lookup.street = "123 Main St"; + let expectedParameters = { + street: "123 Main St", + match: "enhanced", + candidates: 5, }; client.send(lookup); @@ -64,6 +81,133 @@ describe("A US Street client", function () { expect(mockSender.request.parameters).to.deep.equal(expectedParameters); }); + it("sends no match or candidates when match type is strict.", function () { + let mockSender = new MockSender(); + const client = new Client(mockSender); + let lookup = new Lookup(); + lookup.street = "123 Main St"; + lookup.match = "strict"; + let expectedParameters = { + street: "123 Main St", + }; + + client.send(lookup); + + expect(mockSender.request.parameters).to.deep.equal(expectedParameters); + }); + + it("sends candidates but not match when match is strict with explicit candidates.", function () { + let mockSender = new MockSender(); + const client = new Client(mockSender); + let lookup = new Lookup(); + lookup.street = "123 Main St"; + lookup.match = "strict"; + lookup.maxCandidates = 3; + let expectedParameters = { + street: "123 Main St", + candidates: 3, + }; + + client.send(lookup); + + expect(mockSender.request.parameters).to.deep.equal(expectedParameters); + }); + + it("sends match invalid when explicitly set.", function () { + let mockSender = new MockSender(); + const client = new Client(mockSender); + let lookup = new Lookup(); + lookup.match = "invalid"; + let expectedParameters = { + match: "invalid", + }; + + client.send(lookup); + + expect(mockSender.request.parameters).to.deep.equal(expectedParameters); + }); + + it("sends match invalid with explicit candidates.", function () { + let mockSender = new MockSender(); + const client = new Client(mockSender); + let lookup = new Lookup(); + lookup.match = "invalid"; + lookup.maxCandidates = 3; + let expectedParameters = { + match: "invalid", + candidates: 3, + }; + + client.send(lookup); + + expect(mockSender.request.parameters).to.deep.equal(expectedParameters); + }); + + it("defaults maxCandidates to 5 when enhanced and maxCandidates is 0.", function () { + let mockSender = new MockSender(); + const client = new Client(mockSender); + let lookup = new Lookup(); + lookup.match = "enhanced"; + lookup.maxCandidates = 0; + let expectedParameters = { + match: "enhanced", + candidates: 5, + }; + + client.send(lookup); + + expect(mockSender.request.parameters).to.deep.equal(expectedParameters); + }); + + it("uses explicit maxCandidates when enhanced and maxCandidates is set.", function () { + let mockSender = new MockSender(); + const client = new Client(mockSender); + let lookup = new Lookup(); + lookup.match = "enhanced"; + lookup.maxCandidates = 10; + let expectedParameters = { + match: "enhanced", + candidates: 10, + }; + + client.send(lookup); + + expect(mockSender.request.parameters).to.deep.equal(expectedParameters); + }); + + it("does not default candidates for non-enhanced match modes.", function () { + let mockSender = new MockSender(); + const client = new Client(mockSender); + let lookup = new Lookup(); + lookup.street = "123 Main St"; + lookup.match = "invalid"; + // maxCandidates left undefined - should NOT default to 5 for non-enhanced + let expectedParameters = { + street: "123 Main St", + match: "invalid", + }; + + client.send(lookup); + + expect(mockSender.request.parameters).to.deep.equal(expectedParameters); + }); + + it("passes through explicit zero candidates for non-enhanced match modes.", function () { + let mockSender = new MockSender(); + const client = new Client(mockSender); + let lookup = new Lookup(); + lookup.match = "invalid"; + lookup.maxCandidates = 0; + let expectedParameters = { + match: "invalid", + candidates: 0, + }; + + client.send(lookup); + + expect(mockSender.request.parameters).to.deep.equal(expectedParameters); + }); + it("doesn't send an empty batch.", function () { let mockSender = new MockSender(); const client = new Client(mockSender);