diff --git a/.gitignore b/.gitignore index 5e1422c..0a205e5 100644 --- a/.gitignore +++ b/.gitignore @@ -42,9 +42,12 @@ build-iPhoneSimulator/ # for a library or gem, you might want to ignore these files since the code is # intended to run in multiple environments; otherwise, check them in: -# Gemfile.lock +Gemfile.lock # .ruby-version # .ruby-gemset # unless supporting rvm < 1.11.0 or doing something fancy, ignore this: .rvmrc + + +.rspec_status diff --git a/.rspec b/.rspec new file mode 100644 index 0000000..34c5164 --- /dev/null +++ b/.rspec @@ -0,0 +1,3 @@ +--format documentation +--color +--require spec_helper diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..5403acd --- /dev/null +++ b/.travis.yml @@ -0,0 +1,7 @@ +--- +sudo: false +language: ruby +cache: bundler +rvm: + - 2.5 +before_install: gem install bundler -v 1.16.4 diff --git a/Gemfile b/Gemfile new file mode 100644 index 0000000..679377b --- /dev/null +++ b/Gemfile @@ -0,0 +1,6 @@ +source "https://rubygems.org" + +git_source(:github) {|repo_name| "https://github.com/#{repo_name}" } + +# Specify your gem's dependencies in doh_client.gemspec +gemspec diff --git a/README.md b/README.md index e213c1a..e229c08 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,61 @@ # doh_client + +[![Build Status](https://travis-ci.org/ninoseki/doh_client.svg?branch=master)](https://travis-ci.org/ninoseki/doh_client) +[![Maintainability](https://api.codeclimate.com/v1/badges/e9119ee2021f1cfb895d/maintainability)](https://codeclimate.com/github/ninoseki/doh_client/maintainability) +[![Coverage Status](https://coveralls.io/repos/github/ninoseki/doh_client/badge.svg?branch=master)](https://coveralls.io/github/ninoseki/doh_client?branch=master) + DNS over HTTPS(DoH) client for Ruby + +## Installation + +Add this line to your application's Gemfile: + +```ruby +gem 'doh_client' +``` + +And then execute: + + $ bundle + +Or install it yourself as: + + $ gem install doh_client + +## Supported platforms + +- [Google Public DNS](https://developers.google.com/speed/public-dns/docs/dns-over-https) +- [Cloudflare](https://developers.cloudflare.com/1.1.1.1/dns-over-https/) + +## Usage + +### As a Library + +```rb +require 'doh_client' + +DoHClient::Google.resolve("example.com", { type: "A" }) +DoHClient::Google.resolve("example.com", { type: "A", edns_client_subnet: "0.0.0.0/0", random_padding: "XmkMw~o_mgP2pf.gpw-Oi5dK" }) + +DoHClient::Cloudflare.resolve("example.com", { type: "A" }) +DoHClient::Cloudflare.resolve("example.com", { type: "A", do: true, cd: false }) +``` + +### As a CLI + +```bash +$ doh_client +Commands: + console help [COMMAND] # Describe available commands or one specific command + console resolve [NAME] # resolve a given name + +Options: + [--resolver=RESOLVER] # a resolver to use ('google' or 'cloudflare', default: google) + +$ doh_client resolve example.com --type A +# => {"Status":0,"TC":false,"RD":true,"RA":true,"AD":true,"CD":false,"Question":[{"name":"example.com.","type":1}],"Answer":[{"name":"example.com.","type":1,"TTL":5169,"data":"93.184.216.34"}]} +``` + +## License + +The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT). diff --git a/Rakefile b/Rakefile new file mode 100644 index 0000000..b7e9ed5 --- /dev/null +++ b/Rakefile @@ -0,0 +1,6 @@ +require "bundler/gem_tasks" +require "rspec/core/rake_task" + +RSpec::Core::RakeTask.new(:spec) + +task :default => :spec diff --git a/bin/console b/bin/console new file mode 100755 index 0000000..917ac5a --- /dev/null +++ b/bin/console @@ -0,0 +1,14 @@ +#!/usr/bin/env ruby + +require "bundler/setup" +require "doh_client" + +# You can add fixtures and/or initialization code here to make experimenting +# with your gem easier. You can also use a different console, if you like. + +# (If you use this, don't forget to add pry to your Gemfile!) +# require "pry" +# Pry.start + +require "irb" +IRB.start(__FILE__) diff --git a/bin/setup b/bin/setup new file mode 100755 index 0000000..dce67d8 --- /dev/null +++ b/bin/setup @@ -0,0 +1,8 @@ +#!/usr/bin/env bash +set -euo pipefail +IFS=$'\n\t' +set -vx + +bundle install + +# Do any other automated setup that you need to do here diff --git a/doh_client.gemspec b/doh_client.gemspec new file mode 100644 index 0000000..c7ee263 --- /dev/null +++ b/doh_client.gemspec @@ -0,0 +1,36 @@ +# frozen_string_literal: true + +lib = File.expand_path('lib', __dir__) +$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib) +require "doh_client/version" + +Gem::Specification.new do |spec| + spec.name = "doh_client" + spec.version = DoHClient::VERSION + spec.authors = ["Manabu Niseki"] + spec.email = ["manabu.niseki@gmail.com"] + + spec.summary = "DNS over HTTPS(DoH) client for Ruby" + spec.description = "DNS over HTTPS(DoH) client for Ruby" + spec.homepage = "https://github.com/ninoseki/doh_client" + spec.license = "MIT" + + # Specify which files should be added to the gem when it is released. + # The `git ls-files -z` loads the files in the RubyGem that have been added into git. + spec.files = Dir.chdir(File.expand_path(__dir__)) do + `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) } + end + spec.bindir = "exe" + spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) } + spec.require_paths = ["lib"] + + spec.add_development_dependency "bundler", "~> 1.16" + spec.add_development_dependency "coveralls", "~> 0.8" + spec.add_development_dependency "rake", "~> 12.3" + spec.add_development_dependency "rspec", "~> 3.8" + spec.add_development_dependency "vcr", "~> 4.0" + spec.add_development_dependency "webmock", "~> 3.4" + + spec.add_dependency "http", "~> 3.3" + spec.add_dependency "thor", "~> 0.19" +end diff --git a/lib/doh_client.rb b/lib/doh_client.rb new file mode 100644 index 0000000..17571fe --- /dev/null +++ b/lib/doh_client.rb @@ -0,0 +1,15 @@ +# frozen_string_literal: true + +require "doh_client/version" +require "doh_client/error" + +require "doh_client/client/request" +require "doh_client/client/base" +require "doh_client/client/cloudflare" +require "doh_client/client/google" + +require "doh_client/cli" + +module DoHClient + # Your code goes here... +end diff --git a/lib/doh_client/cli.rb b/lib/doh_client/cli.rb new file mode 100644 index 0000000..25b137a --- /dev/null +++ b/lib/doh_client/cli.rb @@ -0,0 +1,42 @@ +# frozen_string_literal: true + +require "thor" +require "json" + +module DoHClient + class CLI < Thor + class_option :resolver, type: :string, desc: "a resolver to use ('google' or 'cloudflare', default: google)" + + desc "resolve [NAME]", "resolve a given name" + method_option :type, type: :string, default: "A" + method_option :cd, type: :boolean + method_option :do, type: :boolean + method_option :edns_client_subnet, type: :string + method_option :random_padding, type: :string + def resolve(name) + hash = resolver.resolve(name, options) + puts hash.to_json + end + + no_commands do + def resolver + case options[:resolver] + when "google" + DoHClient::Client::Google + when "cloudflare" + DoHClient::Client::Cloudflare + else + DoHClient::Client::Google + end + end + + def with_error_handling + yield + rescue ResponseError => e + puts "Warning: #{e}" + rescue ArgumentError => _ + puts "Warning: #{e}" + end + end + end +end diff --git a/lib/doh_client/client/base.rb b/lib/doh_client/client/base.rb new file mode 100644 index 0000000..1a9fd76 --- /dev/null +++ b/lib/doh_client/client/base.rb @@ -0,0 +1,40 @@ +# frozen_string_literal: true + +require "json" +require "http" + +module DoHClient + module Client + class Base + def resolve(name, options) + query = build_query(name, options) + validate(query) + Request.get(endpoint, query) + end + + def self.resolve(name, options) + new.resolve(name, options) + end + + def endpoint + raise NotImplementedError, "You must implement #{self.class}##{__method__}" + end + + def build_query + raise NotImplementedError, "You must implement #{self.class}##{__method__}" + end + + def validate(query) + raise ArgumentError, "name is a required parameter" if query[:name].nil? + raise ArgumentError, "type is a required parameter" if query[:type].nil? + raise ArgumentError, "cd must be a boolean value" if query[:cd] && !boolean?(query[:cd]) + end + + private + + def boolean?(param) + [true, false].include? param + end + end + end +end diff --git a/lib/doh_client/client/cloudflare.rb b/lib/doh_client/client/cloudflare.rb new file mode 100644 index 0000000..8a2d4be --- /dev/null +++ b/lib/doh_client/client/cloudflare.rb @@ -0,0 +1,25 @@ +# frozen_string_literal: true + +module DoHClient + module Client + class Cloudflare < Base + def endpoint + "https://cloudflare-dns.com/dns-query" + end + + def build_query(name, options) + { + name: name, + type: options[:type], + cd: options[:cd], + do: options[:do] + }.compact + end + + def validate(query) + super(query) + raise ArgumentError, "do must be a boolean value" if query[:do] && !boolean?(query[:do]) + end + end + end +end diff --git a/lib/doh_client/client/google.rb b/lib/doh_client/client/google.rb new file mode 100644 index 0000000..77375cd --- /dev/null +++ b/lib/doh_client/client/google.rb @@ -0,0 +1,21 @@ +# frozen_string_literal: true + +module DoHClient + module Client + class Google < Base + def endpoint + "https://dns.google.com/resolve" + end + + def build_query(name, options) + { + name: name, + type: options[:type], + cd: options[:cd], + edns_client_subnet: options[:edns_client_subnet], + random_padding: options[:random_padding] + }.compact + end + end + end +end diff --git a/lib/doh_client/client/request.rb b/lib/doh_client/client/request.rb new file mode 100644 index 0000000..8b46f14 --- /dev/null +++ b/lib/doh_client/client/request.rb @@ -0,0 +1,33 @@ +# frozen_string_literal: true + +module DoHClient + module Client + class Request + def get(url, query) + res = http.headers(headers).get(url, params: query); + return JSON.parse(res.body.to_s) if res.code == 200 + raise ResponseError, res.body.to_s + end + + def self.get(url, query) + new.get(url, query); + end + + def headers + { + accept: "application/dns-json", + user_agent: "curl/7.54.0" + } + end + + def http + if proxy = ENV["HTTPS_RPOXY"] || ENV["https_proxy"] + uri = URI(proxy) + HTTP.via(uri.hostname, uri.port) + else + HTTP + end + end + end + end +end diff --git a/lib/doh_client/error.rb b/lib/doh_client/error.rb new file mode 100644 index 0000000..ffc7523 --- /dev/null +++ b/lib/doh_client/error.rb @@ -0,0 +1,5 @@ +# frozen_string_literal: true + +module DoHClient + class ResponseError < StandardError; end +end diff --git a/lib/doh_client/version.rb b/lib/doh_client/version.rb new file mode 100644 index 0000000..6769522 --- /dev/null +++ b/lib/doh_client/version.rb @@ -0,0 +1,5 @@ +# frozen_string_literal: true + +module DoHClient + VERSION = "0.1.0" +end diff --git a/spec/base_spec.rb b/spec/base_spec.rb new file mode 100644 index 0000000..2520738 --- /dev/null +++ b/spec/base_spec.rb @@ -0,0 +1,18 @@ +# frozen_string_literal: true + +RSpec.describe DoHClient::Client::Base, :vcr do + subject { DoHClient::Client::Base.new } + describe "#validate" do + it "should raise an ArgumentError when given an invaid query" do + q = { type: "A" } + expect { subject.validate q }.to raise_error(ArgumentError) + q = { name: "example.com" } + expect { subject.validate q }.to raise_error(ArgumentError) + + q = { name: "example.com", type: "A", cd: "invalid"} + expect { subject.validate q }.to raise_error(ArgumentError) + q = { name: "example.com", type: "A", cd: false } + subject.validate q + end + end +end diff --git a/spec/cli_spec.rb b/spec/cli_spec.rb new file mode 100644 index 0000000..1078a18 --- /dev/null +++ b/spec/cli_spec.rb @@ -0,0 +1,22 @@ +require "json" + +RSpec.describe DoHClient::CLI, :vcr do + context "google" do + describe "#resolve" do + it "should output a valid JSON" do + output = capture(:stdout) { DoHClient::CLI.start %w(resolve github.com --resolver=google) } + json = JSON.parse(output) + expect(json).to be_a(Hash) + end + end + end + context "cloudflare" do + describe "#resolve" do + it "should output a valid JSON" do + output = capture(:stdout) { DoHClient::CLI.start %w(resolve github.com --resolver=cloudflare) } + json = JSON.parse(output) + expect(json).to be_a(Hash) + end + end + end +end diff --git a/spec/cloudflare_spec.rb b/spec/cloudflare_spec.rb new file mode 100644 index 0000000..0c4766a --- /dev/null +++ b/spec/cloudflare_spec.rb @@ -0,0 +1,22 @@ +# frozen_string_literal: true + +RSpec.describe DoHClient::Client::Cloudflare, :vcr do + describe "#get" do + context "without options" do + it "should return a hash" do + hash = subject.resolve("example.com", type: "A") + expect(hash).to be_a(Hash) + expect(hash.dig("Status")).to eq(0) + expect(hash.dig("Answer").first.dig("name")).to eq("example.com.") + end + end + context "with options" do + it "should return a hash" do + hash = subject.resolve("example.com", type: "A", do: true, cd: false) + expect(hash).to be_a(Hash) + expect(hash.dig("Status")).to eq(0) + expect(hash.dig("Answer").first.dig("name")).to eq("example.com.") + end + end + end +end diff --git a/spec/doh_client_spec.rb b/spec/doh_client_spec.rb new file mode 100644 index 0000000..bff310b --- /dev/null +++ b/spec/doh_client_spec.rb @@ -0,0 +1,5 @@ +RSpec.describe DoHClient do + it "has a version number" do + expect(DoHClient::VERSION).not_to be nil + end +end diff --git a/spec/fixtures/vcr_cassettes/DoHClient_CLI/cloudflare/_resolve/should_output_a_valid_JSON.yml b/spec/fixtures/vcr_cassettes/DoHClient_CLI/cloudflare/_resolve/should_output_a_valid_JSON.yml new file mode 100644 index 0000000..7426e61 --- /dev/null +++ b/spec/fixtures/vcr_cassettes/DoHClient_CLI/cloudflare/_resolve/should_output_a_valid_JSON.yml @@ -0,0 +1,47 @@ +--- +http_interactions: +- request: + method: get + uri: https://cloudflare-dns.com/dns-query?name=github.com&type=A + body: + encoding: UTF-8 + string: '' + headers: + Accept: + - application/dns-json + User-Agent: + - curl/7.54.0 + Connection: + - close + Host: + - cloudflare-dns.com + response: + status: + code: 200 + message: OK + headers: + Date: + - Sun, 26 Aug 2018 01:00:23 GMT + Content-Type: + - application/dns-json + Content-Length: + - '276' + Connection: + - close + Access-Control-Allow-Origin: + - "*" + Cache-Control: + - max-age=25 + Server: + - cloudflare-nginx + Cf-Ray: + - 45025c771b239427-NRT + body: + encoding: ASCII-8BIT + string: '{"Status": 0,"TC": false,"RD": true, "RA": true, "AD": false,"CD": + false,"Question":[{"name": "github.com.", "type": 1}],"Answer":[{"name": "github.com.", + "type": 1, "TTL": 25, "data": "192.30.255.112"},{"name": "github.com.", "type": + 1, "TTL": 25, "data": "192.30.255.113"}]}' + http_version: + recorded_at: Sun, 26 Aug 2018 01:00:23 GMT +recorded_with: VCR 4.0.0 diff --git a/spec/fixtures/vcr_cassettes/DoHClient_CLI/google/_resolve/should_output_a_valid_JSON.yml b/spec/fixtures/vcr_cassettes/DoHClient_CLI/google/_resolve/should_output_a_valid_JSON.yml new file mode 100644 index 0000000..f60dd68 --- /dev/null +++ b/spec/fixtures/vcr_cassettes/DoHClient_CLI/google/_resolve/should_output_a_valid_JSON.yml @@ -0,0 +1,55 @@ +--- +http_interactions: +- request: + method: get + uri: https://dns.google.com/resolve?name=github.com&type=A + body: + encoding: UTF-8 + string: '' + headers: + Accept: + - application/dns-json + User-Agent: + - curl/7.54.0 + Connection: + - close + Host: + - dns.google.com + response: + status: + code: 200 + message: OK + headers: + Strict-Transport-Security: + - max-age=31536000; includeSubDomains; preload + Date: + - Sun, 26 Aug 2018 00:59:38 GMT + Expires: + - Sun, 26 Aug 2018 00:59:38 GMT + Cache-Control: + - private, max-age=59 + Content-Type: + - application/x-javascript; charset=UTF-8 + Server: + - HTTP server (unknown) + X-Xss-Protection: + - 1; mode=block + X-Frame-Options: + - SAMEORIGIN + Alt-Svc: + - quic=":443"; ma=2592000; v="44,43,39,35" + Accept-Ranges: + - none + Vary: + - Accept-Encoding + Connection: + - close + body: + encoding: UTF-8 + string: '{"Status": 0,"TC": false,"RD": true,"RA": true,"AD": false,"CD": false,"Question":[ + {"name": "github.com.","type": 1}],"Answer":[ {"name": "github.com.","type": + 1,"TTL": 59,"data": "192.30.255.112"},{"name": "github.com.","type": 1,"TTL": + 59,"data": "192.30.255.113"}],"Comment": "Response from 208.78.71.16."}' + http_version: + recorded_at: Sun, 26 Aug 2018 00:59:38 GMT +recorded_with: VCR 4.0.0 diff --git a/spec/fixtures/vcr_cassettes/DoHClient_Client_Cloudflare/_get/with_options/should_return_a_hash.yml b/spec/fixtures/vcr_cassettes/DoHClient_Client_Cloudflare/_get/with_options/should_return_a_hash.yml new file mode 100644 index 0000000..d9fb82f --- /dev/null +++ b/spec/fixtures/vcr_cassettes/DoHClient_Client_Cloudflare/_get/with_options/should_return_a_hash.yml @@ -0,0 +1,48 @@ +--- +http_interactions: +- request: + method: get + uri: https://cloudflare-dns.com/dns-query?cd=false&do=true&name=example.com&type=A + body: + encoding: UTF-8 + string: '' + headers: + Accept: + - application/dns-json + User-Agent: + - curl/7.54.0 + Connection: + - close + Host: + - cloudflare-dns.com + response: + status: + code: 200 + message: OK + headers: + Date: + - Sun, 26 Aug 2018 00:59:38 GMT + Content-Type: + - application/dns-json + Content-Length: + - '501' + Connection: + - close + Access-Control-Allow-Origin: + - "*" + Cache-Control: + - max-age=3696 + Server: + - cloudflare-nginx + Cf-Ray: + - 45025b5c38be9427-NRT + body: + encoding: ASCII-8BIT + string: '{"Status": 0,"TC": false,"RD": true, "RA": true, "AD": true,"CD": false,"Question":[{"name": + "example.com.", "type": 1}],"Answer":[{"name": "example.com.", "type": 1, + "TTL": 3696, "data": "93.184.216.34"},{"name": "example.com.", "type": 46, + "TTL": 3696, "data": "A 8 2 86400 20180906081221 20180815232850 63855 example.com. + aghAlx+Pri5fpVkYLMi0Tz/9gKkrl/JUAliu/H4iByPqK9CVNt5p1ajL9A5XzH0FNjggvDIwm8sEVcuB3CvKSVRXdORIwdHf5I2l07nqiqaAh/lobKDHc6+UsTlIGG3V+dlPuQLdNuc5joMoDYk1FR8QiEqIBDOLn125HVC/zm8="}]}' + http_version: + recorded_at: Sun, 26 Aug 2018 00:59:38 GMT +recorded_with: VCR 4.0.0 diff --git a/spec/fixtures/vcr_cassettes/DoHClient_Client_Cloudflare/_get/without_options/should_return_a_hash.yml b/spec/fixtures/vcr_cassettes/DoHClient_Client_Cloudflare/_get/without_options/should_return_a_hash.yml new file mode 100644 index 0000000..f4e513b --- /dev/null +++ b/spec/fixtures/vcr_cassettes/DoHClient_Client_Cloudflare/_get/without_options/should_return_a_hash.yml @@ -0,0 +1,46 @@ +--- +http_interactions: +- request: + method: get + uri: https://cloudflare-dns.com/dns-query?name=example.com&type=A + body: + encoding: UTF-8 + string: '' + headers: + Accept: + - application/dns-json + User-Agent: + - curl/7.54.0 + Connection: + - close + Host: + - cloudflare-dns.com + response: + status: + code: 200 + message: OK + headers: + Date: + - Sun, 26 Aug 2018 00:59:38 GMT + Content-Type: + - application/dns-json + Content-Length: + - '206' + Connection: + - close + Access-Control-Allow-Origin: + - "*" + Cache-Control: + - max-age=3696 + Server: + - cloudflare-nginx + Cf-Ray: + - 45025b5be97ea5ba-NRT + body: + encoding: ASCII-8BIT + string: '{"Status": 0,"TC": false,"RD": true, "RA": true, "AD": true,"CD": false,"Question":[{"name": + "example.com.", "type": 1}],"Answer":[{"name": "example.com.", "type": 1, + "TTL": 3696, "data": "93.184.216.34"}]}' + http_version: + recorded_at: Sun, 26 Aug 2018 00:59:38 GMT +recorded_with: VCR 4.0.0 diff --git a/spec/fixtures/vcr_cassettes/DoHClient_Client_Google/_get/with_options/should_return_a_hash.yml b/spec/fixtures/vcr_cassettes/DoHClient_Client_Google/_get/with_options/should_return_a_hash.yml new file mode 100644 index 0000000..9c41684 --- /dev/null +++ b/spec/fixtures/vcr_cassettes/DoHClient_Client_Google/_get/with_options/should_return_a_hash.yml @@ -0,0 +1,55 @@ +--- +http_interactions: +- request: + method: get + uri: https://dns.google.com/resolve?edns_client_subnet=0.0.0.0/0&name=example.com&random_padding=XmkMw~o_mgP2pf.gpw-Oi5dK&type=A + body: + encoding: UTF-8 + string: '' + headers: + Accept: + - application/dns-json + User-Agent: + - curl/7.54.0 + Connection: + - close + Host: + - dns.google.com + response: + status: + code: 200 + message: OK + headers: + Strict-Transport-Security: + - max-age=31536000; includeSubDomains; preload + Date: + - Sun, 26 Aug 2018 00:59:38 GMT + Expires: + - Sun, 26 Aug 2018 00:59:38 GMT + Cache-Control: + - private, max-age=6495 + Content-Type: + - application/x-javascript; charset=UTF-8 + Server: + - HTTP server (unknown) + X-Xss-Protection: + - 1; mode=block + X-Frame-Options: + - SAMEORIGIN + Alt-Svc: + - quic=":443"; ma=2592000; v="44,43,39,35" + Accept-Ranges: + - none + Vary: + - Accept-Encoding + Connection: + - close + body: + encoding: UTF-8 + string: '{"Status": 0,"TC": false,"RD": true,"RA": true,"AD": true,"CD": false,"Question":[ + {"name": "example.com.","type": 1}],"Answer":[ {"name": "example.com.","type": + 1,"TTL": 6495,"data": "93.184.216.34"}],"Additional":[],"edns_client_subnet": + "0.0.0.0/0"}' + http_version: + recorded_at: Sun, 26 Aug 2018 00:59:38 GMT +recorded_with: VCR 4.0.0 diff --git a/spec/fixtures/vcr_cassettes/DoHClient_Client_Google/_get/without_options/should_return_a_hash.yml b/spec/fixtures/vcr_cassettes/DoHClient_Client_Google/_get/without_options/should_return_a_hash.yml new file mode 100644 index 0000000..6647ad2 --- /dev/null +++ b/spec/fixtures/vcr_cassettes/DoHClient_Client_Google/_get/without_options/should_return_a_hash.yml @@ -0,0 +1,54 @@ +--- +http_interactions: +- request: + method: get + uri: https://dns.google.com/resolve?name=example.com&type=A + body: + encoding: UTF-8 + string: '' + headers: + Accept: + - application/dns-json + User-Agent: + - curl/7.54.0 + Connection: + - close + Host: + - dns.google.com + response: + status: + code: 200 + message: OK + headers: + Strict-Transport-Security: + - max-age=31536000; includeSubDomains; preload + Date: + - Sun, 26 Aug 2018 00:59:38 GMT + Expires: + - Sun, 26 Aug 2018 00:59:38 GMT + Cache-Control: + - private, max-age=5887 + Content-Type: + - application/x-javascript; charset=UTF-8 + Server: + - HTTP server (unknown) + X-Xss-Protection: + - 1; mode=block + X-Frame-Options: + - SAMEORIGIN + Alt-Svc: + - quic=":443"; ma=2592000; v="44,43,39,35" + Accept-Ranges: + - none + Vary: + - Accept-Encoding + Connection: + - close + body: + encoding: UTF-8 + string: '{"Status": 0,"TC": false,"RD": true,"RA": true,"AD": true,"CD": false,"Question":[ + {"name": "example.com.","type": 1}],"Answer":[ {"name": "example.com.","type": + 1,"TTL": 5887,"data": "93.184.216.34"}]}' + http_version: + recorded_at: Sun, 26 Aug 2018 00:59:38 GMT +recorded_with: VCR 4.0.0 diff --git a/spec/google_spec.rb b/spec/google_spec.rb new file mode 100644 index 0000000..d3951ee --- /dev/null +++ b/spec/google_spec.rb @@ -0,0 +1,22 @@ +# frozen_string_literal: true + +RSpec.describe DoHClient::Client::Google, :vcr do + describe "#get" do + context "without options" do + it "should return a hash" do + hash = subject.resolve("example.com", type: "A") + expect(hash).to be_a(Hash) + expect(hash.dig("Status")).to eq(0) + expect(hash.dig("Answer").first.dig("name")).to eq("example.com.") + end + end + context "with options" do + it "should return a hash" do + hash = subject.resolve("example.com", type: "A", edns_client_subnet: "0.0.0.0/0", random_padding: "XmkMw~o_mgP2pf.gpw-Oi5dK") + expect(hash).to be_a(Hash) + expect(hash.dig("Status")).to eq(0) + expect(hash.dig("Answer").first.dig("name")).to eq("example.com.") + end + end + end +end diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb new file mode 100644 index 0000000..68c4c7c --- /dev/null +++ b/spec/spec_helper.rb @@ -0,0 +1,39 @@ +# frozen_string_literal: true + +require "bundler/setup" +require "doh_client" + +require 'coveralls' +Coveralls.wear! + +require "vcr" + +RSpec.configure do |config| + # Enable flags like --only-failures and --next-failure + config.example_status_persistence_file_path = ".rspec_status" + + # Disable RSpec exposing methods globally on `Module` and `main` + config.disable_monkey_patching! + + config.expect_with :rspec do |c| + c.syntax = :expect + end + + def capture(stream) + begin + stream = stream.to_s + eval "$#{stream} = StringIO.new" + yield + result = eval("$#{stream}").string + ensure + eval("$#{stream} = #{stream.upcase}") + end + result + end +end + +VCR.configure do |config| + config.cassette_library_dir = "spec/fixtures/vcr_cassettes" + config.configure_rspec_metadata! + config.hook_into :webmock +end