├── .github └── workflows │ └── build.yml ├── .gitignore ├── .npmignore ├── .npmrc ├── _read.js ├── _write.js ├── index.js ├── package.json ├── readme.md ├── test-form-urlencoded.js ├── test-get.js ├── test-post.js ├── test-promise.js └── test-qq-multipart.js /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: Node CI 2 | 3 | # Push tests commits; pull_request tests PR merges 4 | on: [ push, pull_request ] 5 | 6 | defaults: 7 | run: 8 | shell: bash 9 | 10 | jobs: 11 | 12 | # Test the build 13 | build: 14 | # Setup 15 | runs-on: ${{ matrix.os }} 16 | strategy: 17 | matrix: 18 | node-version: [ 12.x, 14.x, 16.x, 18.x ] 19 | os: [ windows-latest, ubuntu-latest, macOS-latest ] 20 | 21 | # Go 22 | steps: 23 | - name: Check out repo 24 | uses: actions/checkout@v3 25 | 26 | - name: Set up Node.js 27 | uses: actions/setup-node@v3 28 | with: 29 | node-version: ${{ matrix.node-version }} 30 | 31 | - name: Env 32 | run: | 33 | echo "Event name: ${{ github.event_name }}" 34 | echo "Git ref: ${{ github.ref }}" 35 | echo "GH actor: ${{ github.actor }}" 36 | echo "SHA: ${{ github.sha }}" 37 | VER=`node --version`; echo "Node ver: $VER" 38 | VER=`npm --version`; echo "npm ver: $VER" 39 | 40 | - name: Install 41 | run: npm install 42 | 43 | - name: Build 44 | run: npm run build 45 | 46 | - name: Test 47 | run: npm test 48 | env: 49 | CI: true 50 | 51 | # Publish to package registries 52 | publish: 53 | # Setup 54 | needs: build 55 | if: startsWith(github.ref, 'refs/tags/v') 56 | runs-on: ubuntu-latest 57 | 58 | # Go 59 | steps: 60 | - name: Check out repo 61 | uses: actions/checkout@v3 62 | 63 | - name: Set up Node.js 64 | uses: actions/setup-node@v3 65 | with: 66 | node-version: lts/* 67 | registry-url: https://registry.npmjs.org/ 68 | 69 | - name: Install 70 | run: npm install 71 | 72 | - name: Build 73 | run: npm run build 74 | 75 | - name: Publish @latest to npm 76 | if: contains(github.ref, 'RC') == false #'!contains()'' doesn't work lol 77 | run: npm publish 78 | env: 79 | NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} 80 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | bundle.js 2 | dist.js 3 | node_modules/ 4 | package-lock.json 5 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | .* 2 | test-*.js 3 | index.js 4 | _read.js 5 | _write.js 6 | node_modules 7 | -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- 1 | package-lock=false 2 | -------------------------------------------------------------------------------- /_read.js: -------------------------------------------------------------------------------- 1 | var http = require('http') 2 | var https = require('https') 3 | var url = require('url') 4 | var qs = require('querystring') 5 | 6 | module.exports = function _read(httpMethod, options, callback) { 7 | 8 | // deep copy options 9 | options = JSON.parse(JSON.stringify(options)) 10 | 11 | // alias body = data 12 | if (options.body && !options.data) { 13 | options.data = options.body 14 | } 15 | 16 | // require options.url or fail noisily 17 | if (!options.url) { 18 | throw Error('options.url required') 19 | } 20 | 21 | // setup promise if there is no callback 22 | var promise 23 | if (!callback) { 24 | promise = new Promise(function(res, rej) { 25 | callback = function(err, result) { 26 | err ? rej(err) : res(result) 27 | } 28 | }) 29 | } 30 | 31 | // parse out the options from options.url 32 | var opts = url.parse(options.url) 33 | var method = opts.protocol === 'https:' ? https.request : http.request 34 | var defaultContentType = 'application/json; charset=utf-8' 35 | 36 | // check for additional query params 37 | if (options.data) { 38 | var isSearch = !!opts.search 39 | options.url += (isSearch? '&' : '?') + qs.stringify(options.data) 40 | opts = url.parse(options.url) 41 | } 42 | 43 | // add timeout if it exists 44 | if (options.timeout) { 45 | opts.timeout = options.timeout 46 | } 47 | 48 | // wrangle defaults 49 | opts.method = httpMethod 50 | opts.headers = options.headers || {} 51 | opts.headers['user-agent'] = opts.headers['user-agent'] || opts.headers['User-Agent'] || 'tiny-http' 52 | opts.headers['content-type'] = opts.headers['content-type'] || opts.headers['Content-Type'] || defaultContentType 53 | 54 | // make a request 55 | var req = method(opts, function _res(res) { 56 | var raw = [] // keep our buffers here 57 | var ok = res.statusCode >= 200 && res.statusCode < 303 58 | 59 | res.on('data', function _data(chunk) { 60 | raw.push(chunk) 61 | }) 62 | 63 | res.on('end', function _end() { 64 | var err = null 65 | var result = null 66 | var isJSON = res.headers['content-type'] && 67 | (res.headers['content-type'].startsWith('application/json') || 68 | res.headers['content-type'].match(/^application\/.*json/)) 69 | try { 70 | result = Buffer.concat(raw) 71 | 72 | if (!options.buffer) { 73 | var strRes = result.toString() 74 | result = strRes && isJSON ? JSON.parse(strRes) : strRes 75 | } 76 | } 77 | catch(e) { 78 | err = e 79 | } 80 | 81 | if (!ok) { 82 | err = Error('GET failed with: ' + res.statusCode) 83 | err.raw = res 84 | err.body = isJSON? JSON.stringify(result) : result.toString() 85 | err.statusCode = res.statusCode 86 | callback(err) 87 | } 88 | else { 89 | callback(err, {body:result, headers:res.headers}) 90 | } 91 | }) 92 | }) 93 | 94 | req.on('error', callback) 95 | req.end() 96 | 97 | return promise 98 | } 99 | -------------------------------------------------------------------------------- /_write.js: -------------------------------------------------------------------------------- 1 | var qs = require('querystring') 2 | var http = require('http') 3 | var https = require('https') 4 | var FormData = require('@brianleroux/form-data') 5 | var url = require('url') 6 | 7 | module.exports = function _write(httpMethod, options, callback) { 8 | 9 | // deep copy options if no buffers being passed in 10 | let formopts = options.data || options.body 11 | let notplain = k => typeof formopts[k] != 'string' 12 | let basic = formopts && Object.keys(formopts).some(notplain) === false 13 | if (basic) { 14 | options = JSON.parse(JSON.stringify(options)) 15 | } 16 | 17 | // alias body = data 18 | if (options.body && !options.data) { 19 | options.data = options.body 20 | } 21 | 22 | // require options.url or fail noisily 23 | if (!options.url) { 24 | throw Error('options.url required') 25 | } 26 | 27 | // setup promise if there is no callback 28 | var promise 29 | if (!callback) { 30 | promise = new Promise(function(res, rej) { 31 | callback = function(err, result) { 32 | err ? rej(err) : res(result) 33 | } 34 | }) 35 | } 36 | 37 | // parse out the options from options.url 38 | var opts = url.parse(options.url) 39 | var method = opts.protocol === 'https:'? https.request : http.request 40 | var defaultContentType = 'application/json; charset=utf-8' 41 | 42 | // add timeout 43 | if (options.timeout) { 44 | opts.timeout = timeout 45 | } 46 | 47 | // wrangle defaults 48 | opts.method = httpMethod 49 | opts.headers = options.headers || {} 50 | opts.headers['user-agent'] = opts.headers['user-agent'] || opts.headers['User-Agent'] || 'tiny-http' 51 | opts.headers['content-type'] = opts.headers['content-type'] || opts.headers['Content-Type'] || defaultContentType 52 | 53 | // default to regular POST body (url enc) 54 | var postData = qs.stringify(options.data || {}) 55 | 56 | function is(headers, type) { 57 | var regex = type instanceof RegExp 58 | var upper = headers['Content-Type'] 59 | var lower = headers['content-type'] 60 | var isU = upper && (regex ? upper.match(type) : upper.startsWith(type)) 61 | var isL = lower && (regex ? lower.match(type) : lower.startsWith(type)) 62 | return isU || isL 63 | } 64 | 65 | // if we're posting JSON stringify options.data 66 | var isJSON = is(opts.headers, /^application\/.*json/) 67 | if (isJSON) { 68 | postData = JSON.stringify(options.data || {}) 69 | } 70 | 71 | // if we're doing a application/x-www-form-urlencoded to upload files 72 | // we'll overload `method` and use the custom form-data submit instead of http.request 73 | var isUrlEncoded = is(opts.headers, 'application/x-www-form-urlencoded') 74 | if (isUrlEncoded) { 75 | postData = Object.keys(options.data) 76 | .map(k => `${encodeURI(k)}=${encodeURI(options.data[k])}`) 77 | .join('&') 78 | } 79 | 80 | // ensure we know the len ~after~ we set the postData 81 | opts.headers['Content-Length'] = Buffer.byteLength(postData) 82 | 83 | // if we're doing a mutipart/form-data to upload files 84 | // we'll overload `method` and use the custom form-data submit instead of http.request 85 | var isMultipart = is(opts.headers, 'multipart/form-data') 86 | if (isMultipart) { 87 | method = function _multiPartFormDataPost(params, streamback) { 88 | var form = new FormData 89 | Object.keys(options.data).forEach(k=> { 90 | form.append(k, options.data[k]) 91 | }) 92 | // remove stuff generated by form.submit 93 | delete opts.headers['Content-Type'] 94 | delete opts.headers['content-type'] 95 | delete opts.headers['Content-Length'] 96 | delete opts.headers['content-length'] 97 | // perform a multipart/form-data POST request 98 | form.submit(opts, function _submit(err, res) { 99 | if (err) callback(err) 100 | else streamback(res) 101 | }) 102 | } 103 | } 104 | 105 | // make a request 106 | var req = method(opts, function(res) { 107 | var raw = [] // keep our buffers here 108 | var ok = res.statusCode >= 200 && res.statusCode < 303 109 | 110 | res.on('data', function _data(chunk) { 111 | raw.push(chunk) 112 | }) 113 | 114 | res.on('end', function _end() { 115 | var err = null 116 | var result = null 117 | 118 | try { 119 | result = Buffer.concat(raw) 120 | 121 | if (!options.buffer) { 122 | var isJSON = is(res.headers, /^application\/.*json/) 123 | var strRes = result.toString() 124 | result = strRes && isJSON ? JSON.parse(strRes) : strRes 125 | } 126 | } 127 | catch (e) { 128 | err = e 129 | } 130 | 131 | if (!ok) { 132 | err = Error(httpMethod + ' failed with: ' + res.statusCode) 133 | err.raw = res 134 | err.body = result 135 | err.statusCode = res.statusCode 136 | callback(err) 137 | } 138 | else { 139 | callback(err, {body:result, headers:res.headers}) 140 | } 141 | }) 142 | }) 143 | 144 | if (!isMultipart) { 145 | req.on('error', callback) 146 | req.write(postData) 147 | req.end() 148 | } 149 | 150 | return promise 151 | } 152 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | var _read = require('./_read') 2 | var _write = require('./_write') 3 | 4 | module.exports = { 5 | get: _read.bind({}, 'GET'), 6 | head: _read.bind({}, 'HEAD'), 7 | options: _read.bind({}, 'OPTIONS'), 8 | post: _write.bind({}, 'POST'), 9 | put: _write.bind({}, 'PUT'), 10 | patch: _write.bind({}, 'PATCH'), 11 | del: _write.bind({}, 'DELETE'), 12 | delete: _write.bind({}, 'DELETE'), 13 | } 14 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "tiny-json-http", 3 | "version": "7.5.1", 4 | "main": "dist.js", 5 | "scripts": { 6 | "test": "npm run build && tape test-* | tap-spec", 7 | "build": "esbuild index.js --bundle --outfile=dist.js --platform=node" 8 | }, 9 | "repository": { 10 | "type": "git", 11 | "url": "git+https://github.com/brianleroux/tiny-json-http.git" 12 | }, 13 | "author": "Brian LeRoux ", 14 | "license": "Apache-2.0", 15 | "devDependencies": { 16 | "@brianleroux/form-data": "^1.0.3", 17 | "body-parser": "^1.20.2", 18 | "esbuild": "^0.17.10", 19 | "express": "^4.18.2", 20 | "tap-spec": "^5.0.0", 21 | "tape": "^5.6.3", 22 | "uglify-es": "^3.3.7" 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | # tiny-json-http 2 | 3 | Minimalist `HTTP` client for `GET`, `POST`, `PUT`, `PATCH` and `DELETE` `JSON` payloads 4 | 5 | - Zero dependencies: perfect for AWS Lambda 6 | - Sensible default: assumes buffered JSON responses 7 | - System symmetry: Node style errback API, or Promises for use with Async/Await 8 | 9 | ```bash 10 | npm i tiny-json-http --save 11 | ``` 12 | 13 | ### API 14 | 15 | #### Read methods 16 | - `tiny.get(options[, callback])` 17 | - `tiny.head(options[, callback])` 18 | - `tiny.options(options[, callback])` 19 | 20 | #### Write methods 21 | - `tiny.post(options[, callback])` 22 | - `tiny.put(options[, callback])` 23 | - `tiny.patch(options[, callback])` 24 | - `tiny.del(options[, callback)]` 25 | 26 | _*callback is optional, tiny methods will return a promise if no callback is provided_ 27 | 28 | ### Options 29 | 30 | - `url` *required* 31 | - `data` form vars for `tiny.post`, `tiny.put`, `tiny.patch`, and `tiny.delete` otherwise querystring vars for `tiny.get` 32 | - `headers` key/value map used for headers (including support for uploading files with `multipart/form-data`) 33 | - `buffer` if set to `true` the response body is returned as a buffer 34 | 35 | ### Callback values 36 | 37 | - `err` a real javascript `Error` if there was one 38 | - `data` an object with `headers` and `body` keys 39 | 40 | ### Promises 41 | 42 | - if no `callback` is provided to the tiny-json-http methods, a promise is returned 43 | - perfect for use of async/await 44 | 45 | ## Examples 46 | 47 | #### With Async / Await 48 | 49 | ```javascript 50 | var tiny = require('tiny-json-http') 51 | var url = 'http://www.randomkittengenerator.com' 52 | 53 | ;(async function _iife() { 54 | try { 55 | console.log(await tiny.get({url})) 56 | } catch (err) { 57 | console.log('ruh roh!', err) 58 | } 59 | })(); 60 | ``` 61 | 62 | #### With Callback 63 | 64 | ```javascript 65 | var tiny = require('tiny-json-http') 66 | var url = 'http://www.randomkittengenerator.com' 67 | 68 | tiny.get({url}, function _get(err, result) { 69 | if (err) { 70 | console.log('ruh roh!', err) 71 | } 72 | else { 73 | console.log(result) 74 | } 75 | }) 76 | ``` 77 | 78 | Check out the tests for more examples! :heart_decoration: 79 | -------------------------------------------------------------------------------- /test-form-urlencoded.js: -------------------------------------------------------------------------------- 1 | var test = require('tape') 2 | var tiny = require('./dist.js') 3 | var http = require('http') 4 | var server 5 | let body = '' 6 | 7 | test('startup', t=> { 8 | t.plan(1) 9 | server = http.createServer((req, res) => { 10 | req.on('data', chunk => body += chunk) 11 | req.on('end', () => { 12 | res.writeHead(200, { 'Content-Type': 'application/json' }); 13 | res.end(JSON.stringify({ 14 | body: body.concat(), 15 | gotPost:true, 16 | ok:true 17 | })) 18 | }) 19 | }) 20 | server.listen(3000) 21 | t.pass('started server') 22 | }) 23 | 24 | test('supports form-urlencoded bodies', t=> { 25 | t.plan(3) 26 | var url = 'http://localhost:3000' 27 | var data = { foo: 'bar', this: 'is form url encoded!' } 28 | var headers = { 'content-type': 'application/x-www-form-urlencoded' } 29 | tiny.post({url, data, headers}, function __posted(err, result) { 30 | if (err) { 31 | t.fail(err) 32 | } 33 | else { 34 | t.ok(result, 'got a result') 35 | t.ok(result.body.gotPost, 'got a post') 36 | t.equal(result.body.body, 'foo=bar&this=is%20form%20url%20encoded!', 'got form-urlencoded response') 37 | console.log(result) 38 | } 39 | }) 40 | }) 41 | 42 | test('shutdown', t=> { 43 | t.plan(1) 44 | server.close() 45 | t.ok(true, 'closed server') 46 | }) 47 | -------------------------------------------------------------------------------- /test-get.js: -------------------------------------------------------------------------------- 1 | var test = require('tape') 2 | var express = require('express') 3 | var bodyParser = require('body-parser') 4 | var app = express() 5 | var tiny = require('./dist.js') 6 | var server 7 | 8 | app.use(bodyParser.json()) 9 | app.use(bodyParser.urlencoded({extended:true})) 10 | 11 | app.get('/json', (req, res)=> { 12 | res.json({hello: 'there'}) 13 | }) 14 | 15 | app.get('/void', (req, res)=> { 16 | res.json('') 17 | }) 18 | 19 | app.options('/opts', (req, res) => { 20 | res.setHeader('allow', 'OPTIONS, GET') 21 | res.json('') 22 | }) 23 | 24 | app.get('/goaway', (req, res) => { 25 | res.setHeader('location', '/') 26 | res.statusCode = 302 27 | res.send() 28 | }) 29 | 30 | test('startup', t=> { 31 | t.plan(1) 32 | server = app.listen(3001, x=> { 33 | t.ok(true, 'started server') 34 | }) 35 | }) 36 | 37 | test('can get a url', t=> { 38 | t.plan(3) 39 | var url = 'https://brian.io' 40 | tiny.get({url}, function __got(err, result) { 41 | if (err) { 42 | t.fail(err.statusCode, 'failed to get') 43 | } 44 | else { 45 | t.ok(result, 'got a result') 46 | t.ok(result.headers, 'got headers') 47 | t.ok(result.body, 'got body') 48 | console.log(result) 49 | } 50 | }) 51 | }) 52 | 53 | test('can get json', t=> { 54 | t.plan(2) 55 | var url = 'http://localhost:3001/json' 56 | tiny.get({url}, function __json(err, result) { 57 | if (err) { 58 | t.fail(err) 59 | } 60 | else { 61 | t.ok(result, 'got a result') 62 | t.equal(result.body.hello, 'there', 'body is an object') 63 | console.log(err, result) 64 | } 65 | }) 66 | }) 67 | 68 | test('can get and handle "no content"', t=> { 69 | t.plan(2) 70 | var url = 'http://localhost:3001/void' 71 | tiny.get({url}, function __void(err, result) { 72 | if (err) { 73 | t.fail(err) 74 | } 75 | else { 76 | t.ok(result, 'got a result (empty tho)') 77 | t.is(result.body, '') 78 | console.log(result) 79 | } 80 | }) 81 | }) 82 | 83 | 84 | test('get fails gracefully', t=> { 85 | t.plan(1) 86 | var url = 'http://nop333.ca' 87 | tiny.get({url}, function __ruhroh(err, result) { 88 | if (err) { 89 | t.ok(err, 'got err as expected') 90 | console.log(err) 91 | } 92 | else { 93 | t.fail(result, 'should not succeed') 94 | } 95 | }) 96 | }) 97 | 98 | test('can head a url', t=> { 99 | t.plan(2) 100 | var url = 'https://www.google.com' 101 | tiny.head({url}, function __got(err, result) { 102 | if (err) { 103 | t.fail(err.statusCode, 'failed to head') 104 | } 105 | else { 106 | t.ok(result, 'got a result') 107 | t.ok(result.headers, 'got headers') 108 | console.log(JSON.stringify(result, null, 2).substring(0,75), '...') 109 | } 110 | }) 111 | }) 112 | 113 | test('can options a url', t=> { 114 | t.plan(2) 115 | var url = 'http://localhost:3001/opts' 116 | tiny.options({url}, function __got(err, result) { 117 | if (err) { 118 | t.fail(err.statusCode, 'failed to options') 119 | } 120 | else { 121 | t.ok(result, 'got a result') 122 | t.ok(result.headers.allow, 'got headers') 123 | console.log(result) 124 | } 125 | }) 126 | }) 127 | 128 | test('can handles redirect', t=> { 129 | t.plan(2) 130 | var url = 'http://localhost:3001/goaway' 131 | tiny.get({url}, function __got(err, result) { 132 | if (err) { 133 | t.fail(err.statusCode, 'failed to handle redirect') 134 | } else { 135 | t.ok(result, 'got a result') 136 | t.ok(result.headers.location, 'got a location header') 137 | console.log(result) 138 | } 139 | }) 140 | }) 141 | 142 | test('shutdown', t=> { 143 | t.plan(1) 144 | server.close() 145 | t.ok(true, 'closed server') 146 | }) 147 | -------------------------------------------------------------------------------- /test-post.js: -------------------------------------------------------------------------------- 1 | var test = require('tape') 2 | var express = require('express') 3 | var bodyParser = require('body-parser') 4 | var app = express() 5 | var tiny = require('./dist.js') 6 | var server 7 | 8 | app.use(bodyParser.json()) 9 | app.use(bodyParser.urlencoded({extended:true})) 10 | 11 | app.post('/', (req, res)=> { 12 | res.json(Object.assign(req.body, {gotPost:true, ok:true})) 13 | }) 14 | 15 | app.post('/void', (req, res)=> { 16 | res.json('') 17 | }) 18 | 19 | app.put('/', (req, res)=> { 20 | res.json(Object.assign(req.body, {gotPut:true, ok:true})) 21 | }) 22 | 23 | app.patch('/', (req, res)=> { 24 | res.json(Object.assign(req.body, {gotPatch:true, ok:true})) 25 | }) 26 | 27 | app.delete('/', (req, res)=> { 28 | res.json(Object.assign(req.body, {gotDel:true, ok:true})) 29 | }) 30 | 31 | app.post('/goaway', (req, res) => { 32 | res.setHeader('location', '/') 33 | res.statusCode = 302 34 | res.send() 35 | }) 36 | 37 | app.post('/boom', (req, res)=> { 38 | res.setHeader('test-header', 'foo') 39 | res.statusCode = 400 40 | res.json({calls:3}) 41 | }) 42 | 43 | test('startup', t=> { 44 | t.plan(1) 45 | server = app.listen(3000, x=> { 46 | t.ok(true, 'started server') 47 | }) 48 | }) 49 | 50 | test('can post', t=> { 51 | t.plan(2) 52 | var url = 'http://localhost:3000/' 53 | var data = {a:1, b:new Date(Date.now()).toISOString()} 54 | tiny.post({url, data}, function __posted(err, result) { 55 | if (err) { 56 | t.fail(err) 57 | } 58 | else { 59 | t.ok(result, 'got a result') 60 | t.ok(result.body.gotPost, 'got a post') 61 | console.log(result) 62 | } 63 | }) 64 | }) 65 | 66 | test('can post and handle "no content"', t=> { 67 | t.plan(2) 68 | var url = 'http://localhost:3000/void' 69 | var data = {a:1, b:new Date(Date.now()).toISOString()} 70 | tiny.post({url, data}, function __posted(err, result) { 71 | if (err) { 72 | t.fail(err) 73 | } 74 | else { 75 | t.ok(result, 'got a result (empty tho)') 76 | t.is(result.body, '') 77 | console.log(result) 78 | } 79 | }) 80 | }) 81 | 82 | test('can put', t=> { 83 | t.plan(2) 84 | var url = 'http://localhost:3000/' 85 | var data = {a:1, b:new Date(Date.now()).toISOString()} 86 | tiny.put({url, data}, function __posted(err, result) { 87 | if (err) { 88 | t.fail(err) 89 | } 90 | else { 91 | t.ok(result, 'got a result') 92 | t.ok(result.body.gotPut, 'got a put') 93 | console.log(result) 94 | } 95 | }) 96 | }) 97 | 98 | test('can patch', t=> { 99 | t.plan(2) 100 | var url = 'http://localhost:3000/' 101 | var data = {a:1, b:new Date(Date.now()).toISOString()} 102 | tiny.patch({url, data}, function __posted(err, result) { 103 | if (err) { 104 | t.fail(err) 105 | } 106 | else { 107 | t.ok(result, 'got a result') 108 | t.ok(result.body.gotPatch, 'got a patch') 109 | console.log(result) 110 | } 111 | }) 112 | }) 113 | 114 | test('can del', t=> { 115 | t.plan(3) 116 | var url = 'http://localhost:3000/' 117 | var data = {a:1, b:new Date(Date.now()).toISOString()} 118 | tiny.del({url, data}, function __posted(err, result) { 119 | if (err) { 120 | t.fail(err) 121 | } 122 | else { 123 | t.ok(result, 'got a result') 124 | t.ok(result.body.gotDel, 'got a del') 125 | t.ok(result.body.a, 'passed params via query I guess') 126 | console.log(result) 127 | } 128 | }) 129 | }) 130 | 131 | test('can delete (aliased to del)', t=> { 132 | t.plan(3) 133 | var url = 'http://localhost:3000/' 134 | var data = {a:1, b:new Date(Date.now()).toISOString()} 135 | tiny.delete({url, data}, function __posted(err, result) { 136 | if (err) { 137 | t.fail(err) 138 | } 139 | else { 140 | t.ok(result, 'got a result') 141 | t.ok(result.body.gotDel, 'got a del') 142 | t.ok(result.body.a, 'passed params via query I guess') 143 | console.log(result) 144 | } 145 | }) 146 | }) 147 | 148 | test('can access response on errors', t=> { 149 | t.plan(5) 150 | var url = 'http://localhost:3000/boom' 151 | var data = {}; 152 | tiny.post({url, data}, function __posted(err, result) { 153 | t.ok(err, 'got an error') 154 | t.ok(err.raw, 'has raw response') 155 | t.equal(err.raw.statusCode, 400) 156 | t.equal(err.raw.headers['test-header'], 'foo') 157 | t.deepEqual(err.body, {calls: 3}) 158 | }) 159 | }) 160 | 161 | test('can handle redirects', t=> { 162 | t.plan(2) 163 | var url = 'http://localhost:3000/goaway' 164 | var data = {} 165 | tiny.post({url, data}, function __posted(err, result) { 166 | if (err) { 167 | t.fail(err) 168 | t.end() 169 | } else { 170 | t.ok(result, 'got a result') 171 | t.ok(result.headers.location, 'got a location header') 172 | console.log(result) 173 | } 174 | }) 175 | }) 176 | 177 | test('shutdown', t=> { 178 | t.plan(1) 179 | server.close() 180 | t.ok(true, 'closed server') 181 | }) 182 | -------------------------------------------------------------------------------- /test-promise.js: -------------------------------------------------------------------------------- 1 | var test = require('tape') 2 | var tiny = require('./dist.js') 3 | 4 | test('env', t=> { 5 | t.plan(7) 6 | t.ok(tiny, 'got a tiny') 7 | t.ok(tiny.get, 'got a tiny.get') 8 | t.ok(tiny.post, 'got a tiny.post') 9 | t.ok(tiny.put, 'got a tiny.put') 10 | t.ok(tiny.patch, 'got a tiny.patch') 11 | t.ok(tiny.del, 'got a tiny.delete') 12 | t.ok(tiny.delete, 'got a tiny.delete') 13 | console.log(tiny) 14 | }) 15 | 16 | test('can get a url', async t=> { 17 | t.plan(3) 18 | var url = 'https://brian.io' 19 | try { 20 | var result = await tiny.get({url}) 21 | t.ok(result, 'got a result') 22 | t.ok(result.headers, 'got headers') 23 | t.ok(result.body, 'got body') 24 | console.log(result) 25 | } 26 | catch(e) { 27 | t.fail(err.statusCode, 'failed to get') 28 | console.log(err) 29 | } 30 | }) 31 | 32 | test('get fails gracefully', t=> { 33 | t.plan(1) 34 | var url = 'http://nop333.ca' 35 | tiny.get({url}, function __ruhroh(err, result) { 36 | if (err) { 37 | t.ok(err, 'got err as expected') 38 | console.log(err) 39 | } 40 | else { 41 | t.fail(result, 'should not succeed') 42 | } 43 | }) 44 | }) 45 | 46 | test('bad url gives a acually useful error with a fucking line number holy shit', async t=> { 47 | t.plan(1) 48 | try { 49 | var res = await tiny.get('') 50 | t.fail(res) 51 | console.log(res) 52 | } 53 | catch(e) { 54 | t.ok(e, 'res') 55 | console.log(e) 56 | } 57 | }) 58 | -------------------------------------------------------------------------------- /test-qq-multipart.js: -------------------------------------------------------------------------------- 1 | let test = require('tape') 2 | let fs = require('fs') 3 | let path = require('path') 4 | let tiny = require('./dist.js') 5 | let http = require('http') 6 | let port = 3000 7 | let host = 'localhost' 8 | let server 9 | 10 | test('make a multipart post', t=> { 11 | t.plan(1) 12 | t.ok(tiny, 'got env') 13 | }) 14 | 15 | test('start a fake server', t=> { 16 | t.plan(1) 17 | // somebody thought this was intuitive 18 | server = http.createServer((req, res)=> { 19 | let body = [] 20 | req.on('data', chunk => body.push(chunk)) 21 | req.on('end', function _end() { 22 | body = Buffer.concat(body).toString() 23 | res.end(body) 24 | }) 25 | }) 26 | server.listen({port, host}, err=> { 27 | if (err) t.fail(err) 28 | else t.pass(`Started server`) 29 | }) 30 | }) 31 | 32 | test('can multipart/form-data post', t=> { 33 | t.plan(1) 34 | let file = fs.readFileSync(path.join(__dirname, 'readme.md')) 35 | tiny.post({ 36 | url: `http://${host}:${port}`, 37 | headers: { 38 | 'content-type': 'multipart/form-data' 39 | }, 40 | data: { 41 | one: 1, 42 | file 43 | } 44 | }, 45 | function _post(err, data) { 46 | if (err) { 47 | t.fail(err, err) 48 | console.log(err) 49 | } 50 | else { 51 | t.ok(data.body.includes(file.toString()), 'posted') 52 | console.log(data) 53 | } 54 | }) 55 | }) 56 | 57 | test('close fake server', t=> { 58 | t.plan(1) 59 | server.close() 60 | t.ok(true, 'server closed') 61 | }) 62 | --------------------------------------------------------------------------------