New Upstream Snapshot - ruby-github-pages-health-check
Ready changes
Summary
Merged new upstream version: 1.18.1 (was: 1.16.1).
Resulting package
Built on 2022-12-17T12:29 (took 5m59s)
The resulting binary packages can be installed (if you have the apt repository enabled) by running one of:
apt install -t fresh-snapshots ruby-github-pages-health-check
Lintian Result
Diff
diff --git a/.dockerignore b/.dockerignore
new file mode 100644
index 0000000..b844b14
--- /dev/null
+++ b/.dockerignore
@@ -0,0 +1 @@
+Gemfile.lock
diff --git a/.gitignore b/.gitignore
deleted file mode 100644
index b2f83a8..0000000
--- a/.gitignore
+++ /dev/null
@@ -1,7 +0,0 @@
-/*.gem
-*.lock
-.bundle
-vendor/gems
-/bin
-.env
-spec/examples.txt
diff --git a/.rubocop.yml b/.rubocop.yml
index 0f3d8a7..bad031d 100644
--- a/.rubocop.yml
+++ b/.rubocop.yml
@@ -17,7 +17,7 @@
#
AllCops:
- TargetRubyVersion: 2.2
+ TargetRubyVersion: 2.6
Exclude:
- 'bin/**/*'
- 'script/**/*'
@@ -30,10 +30,6 @@ Layout/EndAlignment:
Lint/UnreachableCode:
Severity: error
-Style/StringLiterals:
- EnforcedStyle: double_quotes
- Severity: error
-
Style/StringLiteralsInInterpolation:
EnforcedStyle: double_quotes
@@ -41,10 +37,10 @@ Style/HashSyntax:
EnforcedStyle: hash_rockets
Severity: error
-Layout/AlignHash:
+Layout/HashAlignment:
SupportedLastArgumentHashStyles: always_ignore
-Layout/AlignParameters:
+Layout/ParameterAlignment:
Enabled: false # This is usually true, but we often want to roll back to
# the start of a line.
@@ -55,13 +51,8 @@ Style/Attr:
Style/ClassAndModuleChildren:
Enabled: false # module X<\n>module Y is just as good as module X::Y.
-Style/PercentLiteralDelimiters:
- PreferredDelimiters:
- '%w': '{}'
- '%r': '{}'
-
-Metrics/LineLength:
- Max: 90
+Layout/LineLength:
+ Max: 120
Severity: warning
Exclude:
- github-pages-health-check.gemspec
@@ -99,13 +90,12 @@ Naming/FileName: #Rubocop doesn't like the Git*H*ub namespace
Enabled: false
Metrics/ParameterLists: { Max: 4 }
-Metrics/AbcSize: { Max: 20 }
-Layout/IndentHash: { EnforcedStyle: consistent }
+Layout/FirstHashElementIndentation: { EnforcedStyle: consistent }
Layout/MultilineMethodCallIndentation: { EnforcedStyle: indented }
Layout/MultilineOperationIndentation: { EnforcedStyle: indented }
Layout/FirstParameterIndentation: { EnforcedStyle: consistent }
-Layout/IndentArray: { EnforcedStyle: consistent }
+Layout/FirstArrayElementIndentation: { EnforcedStyle: consistent }
Layout/ExtraSpacing: { AllowForAlignment: true }
Style/SignalException: { EnforcedStyle: only_raise }
Style/StringLiterals: { EnforcedStyle: double_quotes }
@@ -155,3 +145,10 @@ Style/FrozenStringLiteralComment:
Gemspec/RequiredRubyVersion:
Enabled: false
+
+Style/HashEachMethods:
+ Enabled: false
+Style/HashTransformKeys:
+ Enabled: false
+Style/HashTransformValues:
+ Enabled: false
diff --git a/.ruby-version b/.ruby-version
new file mode 100644
index 0000000..860487c
--- /dev/null
+++ b/.ruby-version
@@ -0,0 +1 @@
+2.7.1
diff --git a/.travis.yml b/.travis.yml
deleted file mode 100644
index 66aa871..0000000
--- a/.travis.yml
+++ /dev/null
@@ -1,16 +0,0 @@
-language: ruby
-rvm:
- - 2.4
- - 2.5
- - 2.6
-
-before_install:
- - gem install bundler
-
-script: "script/cibuild"
-
-notifications:
- email: false
-
-cache: bundler
-sudo: false
diff --git a/Dockerfile b/Dockerfile
new file mode 100644
index 0000000..f548631
--- /dev/null
+++ b/Dockerfile
@@ -0,0 +1,18 @@
+ARG RUBY_VERSION
+FROM ruby:$RUBY_VERSION-slim
+RUN set -ex \
+ && gem update --system --silent --quiet \
+ && apt-get update -y \
+ && apt-get upgrade -y \
+ && apt-get install -y \
+ build-essential \
+ git \
+ libcurl4-openssl-dev \
+ && apt-get clean
+WORKDIR /app/github-pages-health-check
+COPY Gemfile .
+COPY github-pages-health-check.gemspec .
+COPY lib/github-pages-health-check/version.rb lib/github-pages-health-check/version.rb
+RUN bundle install
+COPY . .
+ENTRYPOINT [ "/bin/bash" ]
diff --git a/Gemfile b/Gemfile
index be173b2..7ddd7c1 100644
--- a/Gemfile
+++ b/Gemfile
@@ -2,4 +2,14 @@
source "https://rubygems.org"
+group :development do
+ gem "dotenv", "~> 2.7"
+ gem "gem-release", "~> 2.1"
+ gem "pry", "~> 0.10"
+ gem "pry-byebug"
+ gem "rspec", "~> 3.0"
+ gem "rubocop", "~> 0.52"
+ gem "webmock", "~> 3.8"
+end
+
gemspec
diff --git a/README.md b/README.md
index dc0b658..1eb0b8d 100644
--- a/README.md
+++ b/README.md
@@ -81,3 +81,40 @@ check = GitHubPages::HealthCheck::Site.new "github/pages-health-check", access_t
```
You can also set `OCTOKIT_ACCESS_TOKEN` as an environmental variable, or via a `.env` file in your working directory.
+
+### Command Line
+
+```
+./script/check pages.github.com
+
+host: pages.github.com
+uri: https://pages.github.com/
+nameservers: :default
+dns_resolves?: true
+proxied?: false
+cloudflare_ip?: false
+fastly_ip?: false
+old_ip_address?: false
+a_record?: false
+cname_record?: true
+mx_records_present?: false
+valid_domain?: true
+apex_domain?: false
+should_be_a_record?: false
+cname_to_github_user_domain?: true
+cname_to_pages_dot_github_dot_com?: false
+cname_to_fastly?: false
+pointed_to_github_pages_ip?: false
+non_github_pages_ip_present?: false
+pages_domain?: true
+served_by_pages?: true
+valid?: true
+reason:
+https?: true
+enforces_https?: true
+https_error:
+https_eligible?: true
+caa_error:
+dns_zone_soa?: false
+dns_zone_ns?: false
+```
diff --git a/config/cloudflare-ips.txt b/config/cloudflare-ips.txt
index 2800771..fd160bd 100644
--- a/config/cloudflare-ips.txt
+++ b/config/cloudflare-ips.txt
@@ -9,6 +9,14 @@
197.234.240.0/22
198.41.128.0/17
162.158.0.0/15
-104.16.0.0/12
+104.16.0.0/13
+104.24.0.0/14
172.64.0.0/13
131.0.72.0/22
+2400:cb00::/32
+2606:4700::/32
+2803:f800::/32
+2405:b500::/32
+2405:8100::/32
+2a06:98c0::/29
+2c0f:f248::/32
\ No newline at end of file
diff --git a/config/fastly-ips.txt b/config/fastly-ips.txt
index daac7eb..29d9649 100644
--- a/config/fastly-ips.txt
+++ b/config/fastly-ips.txt
@@ -4,9 +4,18 @@
103.245.222.0/23
103.245.224.0/24
104.156.80.0/20
+140.248.64.0/18
+140.248.128.0/17
+146.75.0.0/17
151.101.0.0/16
157.52.64.0/18
+167.82.0.0/17
+167.82.128.0/20
+167.82.160.0/20
+167.82.224.0/20
172.111.64.0/18
185.31.16.0/22
199.27.72.0/21
-199.232.0.0/16
\ No newline at end of file
+199.232.0.0/16
+2a04:4e40::/32
+2a04:4e42::/32
\ No newline at end of file
diff --git a/debian/changelog b/debian/changelog
index c9b6756..fb549c8 100644
--- a/debian/changelog
+++ b/debian/changelog
@@ -1,11 +1,14 @@
-ruby-github-pages-health-check (1.16.1-4) UNRELEASED; urgency=medium
+ruby-github-pages-health-check (1.18.1-1) UNRELEASED; urgency=medium
* Remove constraints unnecessary since buster (oldstable):
+ ruby-github-pages-health-check: Drop versioned constraint on ruby-dnsruby
in Depends.
* Update standards version to 4.6.1, no changes needed.
+ * New upstream release.
+ * Drop patch 0002-Relax-version-dependency-on-publicsuffix-gem.patch, present
+ upstream.
- -- Debian Janitor <janitor@jelmer.uk> Wed, 21 Sep 2022 16:09:37 -0000
+ -- Debian Janitor <janitor@jelmer.uk> Sat, 17 Dec 2022 12:25:57 -0000
ruby-github-pages-health-check (1.16.1-3) unstable; urgency=medium
diff --git a/debian/patches/0001-Replace-git-execution-from-gemspec.patch b/debian/patches/0001-Replace-git-execution-from-gemspec.patch
index 0dd0a0f..8ec8e45 100644
--- a/debian/patches/0001-Replace-git-execution-from-gemspec.patch
+++ b/debian/patches/0001-Replace-git-execution-from-gemspec.patch
@@ -6,10 +6,10 @@ Subject: Replace git execution from gemspec
github-pages-health-check.gemspec | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
-diff --git a/github-pages-health-check.gemspec b/github-pages-health-check.gemspec
-index b6ee220..bab1c19 100644
---- a/github-pages-health-check.gemspec
-+++ b/github-pages-health-check.gemspec
+Index: ruby-github-pages-health-check.git/github-pages-health-check.gemspec
+===================================================================
+--- ruby-github-pages-health-check.git.orig/github-pages-health-check.gemspec
++++ ruby-github-pages-health-check.git/github-pages-health-check.gemspec
@@ -13,7 +13,7 @@ Gem::Specification.new do |s|
s.email = "support@github.com"
s.homepage = "https://github.com/github/github-pages-health-check"
diff --git a/debian/patches/0002-Relax-version-dependency-on-publicsuffix-gem.patch b/debian/patches/0002-Relax-version-dependency-on-publicsuffix-gem.patch
deleted file mode 100644
index f6bbaf1..0000000
--- a/debian/patches/0002-Relax-version-dependency-on-publicsuffix-gem.patch
+++ /dev/null
@@ -1,21 +0,0 @@
-From: =?utf-8?q?C=C3=A9dric_Boutillier?= <boutil@debian.org>
-Date: Tue, 26 Oct 2021 14:46:02 +0200
-Subject: Relax version dependency on publicsuffix gem
-
----
- github-pages-health-check.gemspec | 2 +-
- 1 file changed, 1 insertion(+), 1 deletion(-)
-
-diff --git a/github-pages-health-check.gemspec b/github-pages-health-check.gemspec
-index bab1c19..4363400 100644
---- a/github-pages-health-check.gemspec
-+++ b/github-pages-health-check.gemspec
-@@ -19,7 +19,7 @@ Gem::Specification.new do |s|
- s.add_dependency("addressable", "~> 2.3")
- s.add_dependency("dnsruby", "~> 1.60")
- s.add_dependency("octokit", "~> 4.0")
-- s.add_dependency("public_suffix", "~> 3.0")
-+ s.add_dependency("public_suffix", ">= 3.0", "< 5.0")
- s.add_dependency("typhoeus", "~> 1.3")
-
- s.add_development_dependency("dotenv", "~> 1.0")
diff --git a/debian/patches/series b/debian/patches/series
index 029c171..2b06a4b 100644
--- a/debian/patches/series
+++ b/debian/patches/series
@@ -1,2 +1 @@
0001-Replace-git-execution-from-gemspec.patch
-0002-Relax-version-dependency-on-publicsuffix-gem.patch
diff --git a/github-pages-health-check.gemspec b/github-pages-health-check.gemspec
index b6ee220..87ad564 100644
--- a/github-pages-health-check.gemspec
+++ b/github-pages-health-check.gemspec
@@ -19,13 +19,6 @@ Gem::Specification.new do |s|
s.add_dependency("addressable", "~> 2.3")
s.add_dependency("dnsruby", "~> 1.60")
s.add_dependency("octokit", "~> 4.0")
- s.add_dependency("public_suffix", "~> 3.0")
+ s.add_dependency("public_suffix", ">= 3.0", "< 5.0")
s.add_dependency("typhoeus", "~> 1.3")
-
- s.add_development_dependency("dotenv", "~> 1.0")
- s.add_development_dependency("gem-release", "~> 0.7")
- s.add_development_dependency("pry", "~> 0.10")
- s.add_development_dependency("rspec", "~> 3.0")
- s.add_development_dependency("rubocop", "~> 0.52")
- s.add_development_dependency("webmock", "~> 1.21")
end
diff --git a/lib/github-pages-health-check.rb b/lib/github-pages-health-check.rb
index 2b75724..c14e019 100644
--- a/lib/github-pages-health-check.rb
+++ b/lib/github-pages-health-check.rb
@@ -37,19 +37,9 @@ module GitHubPages
# DNS and HTTP timeout, in seconds
TIMEOUT = 7
- HUMAN_NAME = "GitHub Pages Health Check".freeze
- URL = "https://github.com/github/pages-health-check".freeze
- USER_AGENT = "Mozilla/5.0 (compatible; #{HUMAN_NAME}/#{VERSION}; +#{URL})".freeze
-
- TYPHOEUS_OPTIONS = {
- :followlocation => true,
- :timeout => TIMEOUT,
- :accept_encoding => "gzip",
- :method => :head,
- :headers => {
- "User-Agent" => USER_AGENT
- }
- }.freeze
+ HUMAN_NAME = "GitHub Pages Health Check"
+ URL = "https://github.com/github/pages-health-check"
+ USER_AGENT = "Mozilla/5.0 (compatible; #{HUMAN_NAME}/#{VERSION}; +#{URL})"
# surpress warn-level feedback due to unsupported record types
def self.without_warnings(&block)
@@ -63,5 +53,27 @@ module GitHubPages
def self.check(repository_or_domain, access_token: nil)
Site.new repository_or_domain, :access_token => access_token
end
+
+ # rubocop:disable Naming/AccessorMethodName (this is not an accessor method)
+ def self.set_proxy(proxy_url)
+ @typhoeus_options = typhoeus_options.merge(:proxy => proxy_url).freeze
+ nil
+ end
+ # rubocop:enable Naming/AccessorMethodName
+
+ def self.typhoeus_options
+ return @typhoeus_options if defined?(@typhoeus_options)
+
+ @typhoeus_options = {
+ :followlocation => true,
+ :timeout => TIMEOUT,
+ :accept_encoding => "gzip",
+ :method => :head,
+ :headers => {
+ "User-Agent" => USER_AGENT
+ },
+ :proxy => nil
+ }.freeze
+ end
end
end
diff --git a/lib/github-pages-health-check/caa.rb b/lib/github-pages-health-check/caa.rb
index 3e49774..e9e8a7f 100644
--- a/lib/github-pages-health-check/caa.rb
+++ b/lib/github-pages-health-check/caa.rb
@@ -9,7 +9,7 @@ module GitHubPages
class CAA
attr_reader :host, :error, :nameservers
- def initialize(host, nameservers: :default)
+ def initialize(host:, nameservers: :default)
raise ArgumentError, "host cannot be nil" if host.nil?
@host = host
diff --git a/lib/github-pages-health-check/domain.rb b/lib/github-pages-health-check/domain.rb
index cfc35f6..ff39189 100644
--- a/lib/github-pages-health-check/domain.rb
+++ b/lib/github-pages-health-check/domain.rb
@@ -77,15 +77,25 @@ module GitHubPages
185.199.111.153
).freeze
+ CURRENT_IPV6_ADDRESSES = %w(
+ 2606:50c0:8000::153
+ 2606:50c0:8001::153
+ 2606:50c0:8002::153
+ 2606:50c0:8003::153
+ ).freeze
+
+ CURRENT_IP_ADDRESSES_ALL =
+ (CURRENT_IP_ADDRESSES + CURRENT_IPV6_ADDRESSES).freeze
+
HASH_METHODS = %i[
host uri nameservers dns_resolves? proxied? cloudflare_ip?
- fastly_ip? old_ip_address? a_record? cname_record?
- mx_records_present? valid_domain? apex_domain? should_be_a_record?
- cname_to_github_user_domain? cname_to_pages_dot_github_dot_com?
- cname_to_fastly? pointed_to_github_pages_ip?
- non_github_pages_ip_present? pages_domain?
+ fastly_ip? old_ip_address? a_record? aaaa_record? a_record_present? aaaa_record_present?
+ cname_record? mx_records_present? valid_domain? apex_domain?
+ should_be_a_record? cname_to_github_user_domain?
+ cname_to_pages_dot_github_dot_com? cname_to_fastly?
+ pointed_to_github_pages_ip? non_github_pages_ip_present? pages_domain?
served_by_pages? valid? reason valid_domain? https?
- enforces_https? https_error https_eligible? caa_error
+ enforces_https? https_error https_eligible? caa_error dns_zone_soa? dns_zone_ns?
].freeze
def self.redundant(host)
@@ -100,22 +110,24 @@ module GitHubPages
@host = normalize_host(host)
@nameservers = nameservers
@resolver = GitHubPages::HealthCheck::Resolver.new(self.host,
- :nameservers => nameservers)
+ :nameservers => nameservers)
end
# Runs all checks, raises an error if invalid
+ # rubocop:disable Metrics/AbcSize
def check!
- raise Errors::InvalidDomainError, :domain => self unless valid_domain?
- raise Errors::InvalidDNSError, :domain => self unless dns_resolves?
- raise Errors::DeprecatedIPError, :domain => self if deprecated_ip?
+ raise Errors::InvalidDomainError.new :domain => self unless valid_domain?
+ raise Errors::InvalidDNSError.new :domain => self unless dns_resolves?
+ raise Errors::DeprecatedIPError.new :domain => self if deprecated_ip?
return true if proxied?
- raise Errors::InvalidARecordError, :domain => self if invalid_a_record?
- raise Errors::InvalidCNAMEError, :domain => self if invalid_cname?
- raise Errors::InvalidAAAARecordError, :domain => self if invalid_aaaa_record?
- raise Errors::NotServedByPagesError, :domain => self unless served_by_pages?
+ raise Errors::InvalidARecordError.new :domain => self if invalid_a_record?
+ raise Errors::InvalidCNAMEError.new :domain => self if invalid_cname?
+ raise Errors::InvalidAAAARecordError.new :domain => self if invalid_aaaa_record?
+ raise Errors::NotServedByPagesError.new :domain => self unless served_by_pages?
true
end
+ # rubocop:enable Metrics/AbcSize
def deprecated_ip?
return @deprecated_ip if defined? @deprecated_ip
@@ -126,14 +138,13 @@ module GitHubPages
def invalid_aaaa_record?
return @invalid_aaaa_record if defined? @invalid_aaaa_record
- @invalid_aaaa_record = (valid_domain? && should_be_a_record? &&
- aaaa_record_present?)
+ @invalid_aaaa_record = (valid_domain? && aaaa_record_present? && !should_be_a_record?)
end
def invalid_a_record?
return @invalid_a_record if defined? @invalid_a_record
- @invalid_a_record = (valid_domain? && a_record? && !should_be_a_record?)
+ @invalid_a_record = (valid_domain? && a_record_present? && !should_be_a_record?)
end
def invalid_cname?
@@ -162,7 +173,10 @@ module GitHubPages
# Is this domain an apex domain, meaning a CNAME would be innapropriate
def apex_domain?
return @apex_domain if defined?(@apex_domain)
- return unless valid_domain?
+
+ return false unless valid_domain?
+
+ return true if dns_zone_soa? && dns_zone_ns?
# PublicSuffix.domain pulls out the apex-level domain name.
# E.g. PublicSuffix.domain("techblog.netflix.com") # => "netflix.com"
@@ -175,6 +189,30 @@ module GitHubPages
:ignore_private => true) == unicode_host
end
+ #
+ # Does the domain have an associated SOA record?
+ #
+ def dns_zone_soa?
+ return @soa_records if defined?(@soa_records)
+ return false unless dns?
+
+ @soa_records = dns.any? do |answer|
+ answer.type == Dnsruby::Types::SOA && answer.name.to_s == host
+ end
+ end
+
+ #
+ # Does the domain have assoicated NS records?
+ #
+ def dns_zone_ns?
+ return @ns_records if defined?(@ns_records)
+ return false unless dns?
+
+ @ns_records = dns.any? do |answer|
+ answer.type == Dnsruby::Types::NS && answer.name.to_s == host
+ end
+ end
+
# Should the domain use an A record?
def should_be_a_record?
!pages_io_domain? && (apex_domain? || mx_records_present?)
@@ -184,20 +222,20 @@ module GitHubPages
!should_be_a_record?
end
- # Is the domain's first response an A record to a valid GitHub Pages IP?
+ # Is the domain's first response an A or AAAA record to a valid GitHub Pages IP?
def pointed_to_github_pages_ip?
- a_record? && CURRENT_IP_ADDRESSES.include?(dns.first.address.to_s)
+ return false unless address_record?
+
+ CURRENT_IP_ADDRESSES_ALL.include?(dns.first.address.to_s.downcase)
end
- # Are any of the domain's A records pointing elsewhere?
+ # Are any of the domain's A or AAAA records pointing elsewhere?
def non_github_pages_ip_present?
return unless dns?
- a_records = dns.select { |answer| answer.type == Dnsruby::Types::A }
-
- a_records.any? { |answer| !github_pages_ip?(answer.address.to_s) }
-
- false
+ dns
+ .select { |a| Dnsruby::Types::A == a.type || Dnsruby::Types::AAAA == a.type }
+ .any? { |a| !github_pages_ip?(a.address.to_s) }
end
# Is the domain's first response a CNAME to a pages domain?
@@ -276,7 +314,9 @@ module GitHubPages
Dnsruby::Types::A,
Dnsruby::Types::AAAA,
Dnsruby::Types::CNAME,
- Dnsruby::Types::MX
+ Dnsruby::Types::MX,
+ Dnsruby::Types::NS,
+ Dnsruby::Types::SOA
].freeze
# Returns an array of DNS answers
@@ -314,15 +354,32 @@ module GitHubPages
# Is this domain's first response an A record?
def a_record?
+ return @is_a_record if defined?(@is_a_record)
+ return unless dns?
+
+ @is_a_record = Dnsruby::Types::A == dns.first.type
+ end
+
+ # Is this domain's first response an AAAA record?
+ def aaaa_record?
+ return @is_aaaa_record if defined?(@is_aaaa_record)
+ return unless dns?
+
+ @is_aaaa_record = Dnsruby::Types::AAAA == dns.first.type
+ end
+
+ # Does this domain has an A record setup (not necessarily as the first record)?
+ def a_record_present?
return unless dns?
- dns.first.type == Dnsruby::Types::A
+ dns.any? { |answer| answer.type == Dnsruby::Types::A && answer.name.to_s == host }
end
+ # Does this domain has an AAAA record setup (not necessarily as the first record)?
def aaaa_record_present?
return unless dns?
- dns.any? { |answer| answer.type == Dnsruby::Types::AAAA }
+ dns.any? { |answer| answer.type == Dnsruby::Types::AAAA && answer.name.to_s == host }
end
# Is this domain's first response a CNAME record?
@@ -337,6 +394,8 @@ module GitHubPages
# The domain to which this domain's CNAME resolves
# Returns nil if the domain is not a CNAME
def cname
+ return unless dns?
+
cnames = dns.take_while { |answer| answer.type == Dnsruby::Types::CNAME }
return if cnames.empty?
@@ -354,11 +413,10 @@ module GitHubPages
return unless dns_resolves?
@served_by_pages = begin
- return false unless response.mock? || response.return_code == :ok
return true if response.headers["Server"] == "GitHub.com"
# Typhoeus mangles the case of the header, compare insensitively
- response.headers.any? { |k, _v| k =~ /X-GitHub-Request-Id/i }
+ response.headers.any? { |k, _v| k.downcase == "x-github-request-id" }
end
end
@@ -391,10 +449,11 @@ module GitHubPages
def https_eligible?
# Can't have any IP's which aren't GitHub's present.
return false if non_github_pages_ip_present?
- # Can't have any AAAA records present
- return false if aaaa_record_present?
- # Must be a CNAME or point to our IPs.
+ # Can't have underscores in the domain name (Let's Encrypt does not allow it)
+ return false if host.include?("_")
+
+ # Must be a CNAME or point to our IPs.
# Only check the one domain if a CNAME. Don't check the parent domain.
return true if cname_to_github_user_domain?
@@ -404,27 +463,34 @@ module GitHubPages
# Any errors querying CAA records
def caa_error
- return nil unless caa.errored?
+ return nil unless caa&.errored?
caa.error.class.name
end
private
+ def address_record?
+ a_record? || aaaa_record?
+ end
+
def caa
- @caa ||= GitHubPages::HealthCheck::CAA.new(host, :nameservers => nameservers)
+ @caa ||= GitHubPages::HealthCheck::CAA.new(
+ :host => cname&.host || host,
+ :nameservers => nameservers
+ )
end
# The domain's response to HTTP(S) requests, following redirects
def response
return @response if defined? @response
- @response = Typhoeus.head(uri, TYPHOEUS_OPTIONS)
+ @response = Typhoeus.head(uri, GitHubPages::HealthCheck.typhoeus_options)
# Workaround for webmock not playing nicely with Typhoeus redirects
# See https://github.com/bblimke/webmock/issues/237
if @response.mock? && @response.headers["Location"]
- @response = Typhoeus.head(response.headers["Location"], TYPHOEUS_OPTIONS)
+ @response = Typhoeus.head(response.headers["Location"], GitHubPages::HealthCheck.typhoeus_options)
end
@response
@@ -432,13 +498,13 @@ module GitHubPages
# The domain's response to HTTP requests, without following redirects
def http_response
- options = TYPHOEUS_OPTIONS.merge(:followlocation => false)
+ options = GitHubPages::HealthCheck.typhoeus_options.merge(:followlocation => false)
@http_response ||= Typhoeus.head(uri(:scheme => "http"), options)
end
# The domain's response to HTTPS requests, without following redirects
def https_response
- options = TYPHOEUS_OPTIONS.merge(:followlocation => false)
+ options = GitHubPages::HealthCheck.typhoeus_options.merge(:followlocation => false)
@https_response ||= Typhoeus.head(uri(:scheme => "https"), options)
end
@@ -482,10 +548,12 @@ module GitHubPages
def cdn_ip?(cdn)
return unless dns?
- a_records = dns.select { |answer| answer.type == Dnsruby::Types::A }
- return false if !a_records || a_records.empty?
+ address_records = dns.select do |answer|
+ Dnsruby::Types::A == answer.type || Dnsruby::Types::AAAA == answer.type
+ end
+ return false if !address_records || address_records.empty?
- a_records.all? do |answer|
+ address_records.all? do |answer|
cdn.controls_ip?(answer.address)
end
end
@@ -495,7 +563,7 @@ module GitHubPages
end
def github_pages_ip?(ip_addr)
- CURRENT_IP_ADDRESSES.include?(ip_addr)
+ CURRENT_IP_ADDRESSES_ALL.include?(ip_addr&.to_s&.downcase)
end
end
end
diff --git a/lib/github-pages-health-check/error.rb b/lib/github-pages-health-check/error.rb
index f478182..61b4018 100644
--- a/lib/github-pages-health-check/error.rb
+++ b/lib/github-pages-health-check/error.rb
@@ -3,8 +3,8 @@
module GitHubPages
module HealthCheck
class Error < StandardError
- DOCUMENTATION_BASE = "https://help.github.com".freeze
- DOCUMENTATION_PATH = "/categories/github-pages-basics/".freeze
+ DOCUMENTATION_BASE = "https://help.github.com"
+ DOCUMENTATION_PATH = "/categories/github-pages-basics/"
LOCAL_ONLY = false # Error is only used when running locally
attr_reader :repository, :domain
diff --git a/lib/github-pages-health-check/errors.rb b/lib/github-pages-health-check/errors.rb
index 7ef1c04..6c25e80 100644
--- a/lib/github-pages-health-check/errors.rb
+++ b/lib/github-pages-health-check/errors.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-Dir[File.expand_path("errors/*_error.rb", __dir__)].each do |f|
+Dir[File.expand_path("errors/*_error.rb", __dir__)].sort.each do |f|
require f
end
diff --git a/lib/github-pages-health-check/errors/build_error.rb b/lib/github-pages-health-check/errors/build_error.rb
index 438c4d9..ca83563 100644
--- a/lib/github-pages-health-check/errors/build_error.rb
+++ b/lib/github-pages-health-check/errors/build_error.rb
@@ -4,7 +4,7 @@ module GitHubPages
module HealthCheck
module Errors
class BuildError < GitHubPages::HealthCheck::Error
- DOCUMENTATION_PATH = "/articles/troubleshooting-jekyll-builds/".freeze
+ DOCUMENTATION_PATH = "/articles/troubleshooting-jekyll-builds/"
LOCAL_ONLY = true
end
end
diff --git a/lib/github-pages-health-check/errors/deprecated_ip_error.rb b/lib/github-pages-health-check/errors/deprecated_ip_error.rb
index d4e5d44..fff3f52 100644
--- a/lib/github-pages-health-check/errors/deprecated_ip_error.rb
+++ b/lib/github-pages-health-check/errors/deprecated_ip_error.rb
@@ -4,7 +4,7 @@ module GitHubPages
module HealthCheck
module Errors
class DeprecatedIPError < GitHubPages::HealthCheck::Error
- DOCUMENTATION_PATH = "/articles/setting-up-a-custom-domain-with-github-pages/".freeze
+ DOCUMENTATION_PATH = "/articles/setting-up-a-custom-domain-with-github-pages/"
def message
<<-MSG
diff --git a/lib/github-pages-health-check/errors/invalid_a_record_error.rb b/lib/github-pages-health-check/errors/invalid_a_record_error.rb
index 8563a9e..3becf88 100644
--- a/lib/github-pages-health-check/errors/invalid_a_record_error.rb
+++ b/lib/github-pages-health-check/errors/invalid_a_record_error.rb
@@ -4,7 +4,7 @@ module GitHubPages
module HealthCheck
module Errors
class InvalidARecordError < GitHubPages::HealthCheck::Error
- DOCUMENTATION_PATH = "/articles/setting-up-a-custom-domain-with-github-pages/".freeze
+ DOCUMENTATION_PATH = "/articles/setting-up-a-custom-domain-with-github-pages/"
def message
<<-MSG
diff --git a/lib/github-pages-health-check/errors/invalid_aaaa_record_error.rb b/lib/github-pages-health-check/errors/invalid_aaaa_record_error.rb
index 8afa42c..2d8ed7c 100644
--- a/lib/github-pages-health-check/errors/invalid_aaaa_record_error.rb
+++ b/lib/github-pages-health-check/errors/invalid_aaaa_record_error.rb
@@ -4,13 +4,13 @@ module GitHubPages
module HealthCheck
module Errors
class InvalidAAAARecordError < GitHubPages::HealthCheck::Error
- DOCUMENTATION_PATH = "/articles/setting-up-a-custom-domain-with-github-pages/".freeze
+ DOCUMENTATION_PATH = "/articles/setting-up-a-custom-domain-with-github-pages/"
def message
<<-MSG
- Your site's DNS settings are using a custom subdomain, #{domain.host},
- that's set up with an AAAA record. GitHub Pages currently does not support
- IPv6.
+ Your site's DNS settings are using a custom subdomain, #{domain.host},
+ that's set up as an AAAA record. We recommend you change this to a CNAME
+ record pointing at #{username}.github.io.
MSG
end
end
diff --git a/lib/github-pages-health-check/errors/invalid_cname_error.rb b/lib/github-pages-health-check/errors/invalid_cname_error.rb
index 5bd3669..e62c04c 100644
--- a/lib/github-pages-health-check/errors/invalid_cname_error.rb
+++ b/lib/github-pages-health-check/errors/invalid_cname_error.rb
@@ -4,7 +4,7 @@ module GitHubPages
module HealthCheck
module Errors
class InvalidCNAMEError < GitHubPages::HealthCheck::Error
- DOCUMENTATION_PATH = "/articles/setting-up-a-custom-domain-with-github-pages/".freeze
+ DOCUMENTATION_PATH = "/articles/setting-up-a-custom-domain-with-github-pages/"
def message
<<-MSG
diff --git a/lib/github-pages-health-check/errors/invalid_dns_error.rb b/lib/github-pages-health-check/errors/invalid_dns_error.rb
index 85c197d..9fa9bc8 100644
--- a/lib/github-pages-health-check/errors/invalid_dns_error.rb
+++ b/lib/github-pages-health-check/errors/invalid_dns_error.rb
@@ -4,7 +4,7 @@ module GitHubPages
module HealthCheck
module Errors
class InvalidDNSError < GitHubPages::HealthCheck::Error
- DOCUMENTATION_PATH = "/articles/setting-up-a-custom-domain-with-github-pages/".freeze
+ DOCUMENTATION_PATH = "/articles/setting-up-a-custom-domain-with-github-pages/"
def message
"Domain's DNS record could not be retrieved"
diff --git a/lib/github-pages-health-check/errors/invalid_domain_error.rb b/lib/github-pages-health-check/errors/invalid_domain_error.rb
index adbb56c..c33cd25 100644
--- a/lib/github-pages-health-check/errors/invalid_domain_error.rb
+++ b/lib/github-pages-health-check/errors/invalid_domain_error.rb
@@ -4,7 +4,7 @@ module GitHubPages
module HealthCheck
module Errors
class InvalidDomainError < GitHubPages::HealthCheck::Error
- DOCUMENTATION_PATH = "/articles/setting-up-a-custom-domain-with-github-pages/".freeze
+ DOCUMENTATION_PATH = "/articles/setting-up-a-custom-domain-with-github-pages/"
def message
"Domain is not a valid domain"
diff --git a/lib/github-pages-health-check/errors/not_served_by_pages_error.rb b/lib/github-pages-health-check/errors/not_served_by_pages_error.rb
index d1b7349..7b3c57c 100644
--- a/lib/github-pages-health-check/errors/not_served_by_pages_error.rb
+++ b/lib/github-pages-health-check/errors/not_served_by_pages_error.rb
@@ -4,7 +4,7 @@ module GitHubPages
module HealthCheck
module Errors
class NotServedByPagesError < GitHubPages::HealthCheck::Error
- DOCUMENTATION_PATH = "/articles/setting-up-a-custom-domain-with-github-pages/".freeze
+ DOCUMENTATION_PATH = "/articles/setting-up-a-custom-domain-with-github-pages/"
def message
"Domain does not resolve to the GitHub Pages server"
diff --git a/lib/github-pages-health-check/printer.rb b/lib/github-pages-health-check/printer.rb
index 5cb3ff1..0004a8d 100644
--- a/lib/github-pages-health-check/printer.rb
+++ b/lib/github-pages-health-check/printer.rb
@@ -4,7 +4,7 @@ module GitHubPages
module HealthCheck
class Printer
PRETTY_LEFT_WIDTH = 11
- PRETTY_JOINER = " | ".freeze
+ PRETTY_JOINER = " | "
attr_reader :health_check
diff --git a/lib/github-pages-health-check/repository.rb b/lib/github-pages-health-check/repository.rb
index 4bf2a1f..800a5c4 100644
--- a/lib/github-pages-health-check/repository.rb
+++ b/lib/github-pages-health-check/repository.rb
@@ -47,11 +47,11 @@ module GitHubPages
alias reason build_error
def build_duration
- last_build && last_build.duration
+ last_build&.duration
end
def last_built
- last_build && last_build.updated_at
+ last_build&.updated_at
end
def domain
diff --git a/lib/github-pages-health-check/resolver.rb b/lib/github-pages-health-check/resolver.rb
index 009fa84..9d161d7 100644
--- a/lib/github-pages-health-check/resolver.rb
+++ b/lib/github-pages-health-check/resolver.rb
@@ -43,16 +43,16 @@ module GitHubPages
self.class.default_resolver
when :authoritative
Dnsruby::Resolver.new(DEFAULT_RESOLVER_OPTIONS.merge(
- :nameservers => authoritative_nameservers
- ))
+ :nameservers => authoritative_nameservers
+ ))
when :public
Dnsruby::Resolver.new(DEFAULT_RESOLVER_OPTIONS.merge(
- :nameservers => PUBLIC_NAMESERVERS
- ))
+ :nameservers => PUBLIC_NAMESERVERS
+ ))
when Array
Dnsruby::Resolver.new(DEFAULT_RESOLVER_OPTIONS.merge(
- :nameservers => nameservers
- ))
+ :nameservers => nameservers
+ ))
else
raise "Invalid nameserver type: #{nameservers.inspect}"
end
diff --git a/lib/github-pages-health-check/version.rb b/lib/github-pages-health-check/version.rb
index 9b8d9dd..86b9bdf 100644
--- a/lib/github-pages-health-check/version.rb
+++ b/lib/github-pages-health-check/version.rb
@@ -2,6 +2,6 @@
module GitHubPages
module HealthCheck
- VERSION = "1.16.1".freeze
+ VERSION = "1.18.1"
end
end
diff --git a/script/check b/script/check
index 2328b0a..742daff 100755
--- a/script/check
+++ b/script/check
@@ -3,6 +3,8 @@
#
# Usage: script/check [DOMAIN]
+require "rubygems"
+require "bundler/setup"
require_relative "../lib/github-pages-health-check"
if ARGV.count != 1
diff --git a/script/cibuild b/script/cibuild
index e311b09..7831760 100755
--- a/script/cibuild
+++ b/script/cibuild
@@ -4,7 +4,7 @@ set -ex
script/bootstrap
-script/test
+script/test $@
script/check-cdn-ips
bundle exec script/check www.parkermoore.de | grep 'valid?: true'
bundle exec script/check ben.balter.com | grep 'valid?: true'
diff --git a/script/cibuild-docker b/script/cibuild-docker
new file mode 100755
index 0000000..605b3dd
--- /dev/null
+++ b/script/cibuild-docker
@@ -0,0 +1,11 @@
+#!/bin/bash
+
+: ${RUBY_VERSION:="2.7"}
+docker build -t github-pages-health-check --build-arg RUBY_VERSION=$RUBY_VERSION .
+if [ -n "$DEBUG" ]; then
+ # Run a shell.
+ docker run -it --rm -v $(pwd):/app/github-pages-health-check github-pages-health-check
+else
+ # Run CI
+ docker run --rm github-pages-health-check script/cibuild --profile --fail-fast
+fi
diff --git a/script/test b/script/test
index ca04f20..6eead4a 100755
--- a/script/test
+++ b/script/test
@@ -3,4 +3,4 @@
set -e
bundle exec rspec $@
-script/fmt $@
+script/fmt
diff --git a/script/update-cdn-ips b/script/update-cdn-ips
index 1cbfa00..a496f4f 100755
--- a/script/update-cdn-ips
+++ b/script/update-cdn-ips
@@ -8,15 +8,43 @@ require "open-uri"
require "json"
SOURCES = {
- :cloudflare => "https://www.cloudflare.com/ips-v4",
- :fastly => "https://api.fastly.com/public-ip-list"
+ :cloudflare => ["https://www.cloudflare.com/ips-v4", "https://www.cloudflare.com/ips-v6"],
+ :fastly => ["https://api.fastly.com/public-ip-list"]
}.freeze
-SOURCES.each do |source, url|
+def parse_fastly(data)
+ json_data = JSON.parse(data)
+ (json_data["addresses"] + json_data["ipv6_addresses"]).join("\n")
+end
+
+def parse_cloudflare(data)
+ data
+end
+
+def fetch_ips_from_cdn(urls)
+ urls.map do |url|
+ puts "Fetching #{url}..."
+ URI.parse(url).open.read
+ end.join("\n")
+end
+
+def update_cdn_file(source, data)
file = "config/#{source}-ips.txt"
- puts "Fetching #{url}..."
- data = open(url).read
- data = JSON.parse(data)["addresses"].join("\n") if source == :fastly
File.write(file, data)
+ puts "Writing contents to #{file} and staging changes."
`git add --verbose #{file}`
end
+
+def parse_cdn_response(source, ips)
+ send("parse_#{source}", ips)
+end
+
+def update_cdn_ips(source, urls)
+ ips = fetch_ips_from_cdn(urls)
+ data = parse_cdn_response(source, ips)
+ update_cdn_file(source, data)
+end
+
+SOURCES.each do |source, urls|
+ update_cdn_ips(source, urls)
+end
diff --git a/spec/fixtures/build_error.json b/spec/fixtures/build_error.json
new file mode 100644
index 0000000..20e8be4
--- /dev/null
+++ b/spec/fixtures/build_error.json
@@ -0,0 +1,30 @@
+{
+ "url": "https://api.github.com/repos/github/developer.github.com/pages/builds/5472601",
+ "status": "errored",
+ "error": {
+ "message": "Some message"
+ },
+ "pusher": {
+ "login": "octocat",
+ "id": 1,
+ "avatar_url": "https://github.com/images/error/octocat_happy.gif",
+ "gravatar_id": "",
+ "url": "https://api.github.com/users/octocat",
+ "html_url": "https://github.com/octocat",
+ "followers_url": "https://api.github.com/users/octocat/followers",
+ "following_url": "https://api.github.com/users/octocat/following{/other_user}",
+ "gists_url": "https://api.github.com/users/octocat/gists{/gist_id}",
+ "starred_url": "https://api.github.com/users/octocat/starred{/owner}{/repo}",
+ "subscriptions_url": "https://api.github.com/users/octocat/subscriptions",
+ "organizations_url": "https://api.github.com/users/octocat/orgs",
+ "repos_url": "https://api.github.com/users/octocat/repos",
+ "events_url": "https://api.github.com/users/octocat/events{/privacy}",
+ "received_events_url": "https://api.github.com/users/octocat/received_events",
+ "type": "User",
+ "site_admin": false
+ },
+ "commit": "351391cdcb88ffae71ec3028c91f375a8036a26b",
+ "duration": 2104,
+ "created_at": "2014-02-10T19:00:49Z",
+ "updated_at": "2014-02-10T19:00:51Z"
+}
diff --git a/spec/fixtures/build_success.json b/spec/fixtures/build_success.json
new file mode 100644
index 0000000..e62528d
--- /dev/null
+++ b/spec/fixtures/build_success.json
@@ -0,0 +1,30 @@
+{
+ "url": "https://api.github.com/repos/github/developer.github.com/pages/builds/5472601",
+ "status": "built",
+ "error": {
+ "message": null
+ },
+ "pusher": {
+ "login": "octocat",
+ "id": 1,
+ "avatar_url": "https://github.com/images/error/octocat_happy.gif",
+ "gravatar_id": "",
+ "url": "https://api.github.com/users/octocat",
+ "html_url": "https://github.com/octocat",
+ "followers_url": "https://api.github.com/users/octocat/followers",
+ "following_url": "https://api.github.com/users/octocat/following{/other_user}",
+ "gists_url": "https://api.github.com/users/octocat/gists{/gist_id}",
+ "starred_url": "https://api.github.com/users/octocat/starred{/owner}{/repo}",
+ "subscriptions_url": "https://api.github.com/users/octocat/subscriptions",
+ "organizations_url": "https://api.github.com/users/octocat/orgs",
+ "repos_url": "https://api.github.com/users/octocat/repos",
+ "events_url": "https://api.github.com/users/octocat/events{/privacy}",
+ "received_events_url": "https://api.github.com/users/octocat/received_events",
+ "type": "User",
+ "site_admin": false
+ },
+ "commit": "351391cdcb88ffae71ec3028c91f375a8036a26b",
+ "duration": 2104,
+ "created_at": "2014-02-10T19:00:49Z",
+ "updated_at": "2014-02-10T19:00:51Z"
+}
diff --git a/spec/fixtures/pages_info.json b/spec/fixtures/pages_info.json
new file mode 100644
index 0000000..ca8b6f0
--- /dev/null
+++ b/spec/fixtures/pages_info.json
@@ -0,0 +1,6 @@
+{
+ "url": "https://api.github.com/repos/github/pages.github.com/pages",
+ "status": "built",
+ "cname": "pages.github.com",
+ "custom_404": false
+}
diff --git a/spec/fixtures/pages_info_no_cname.json b/spec/fixtures/pages_info_no_cname.json
new file mode 100644
index 0000000..dfc7414
--- /dev/null
+++ b/spec/fixtures/pages_info_no_cname.json
@@ -0,0 +1,6 @@
+{
+ "url": "https://api.github.com/repos/github/pages.github.com/pages",
+ "status": "built",
+ "cname": null,
+ "custom_404": false
+}
diff --git a/spec/github_pages_health_check/caa_spec.rb b/spec/github_pages_health_check/caa_spec.rb
new file mode 100644
index 0000000..f16d525
--- /dev/null
+++ b/spec/github_pages_health_check/caa_spec.rb
@@ -0,0 +1,94 @@
+# frozen_string_literal: true
+
+require "spec_helper"
+
+RSpec.describe(GitHubPages::HealthCheck::CAA) do
+ let(:domain) { "foo.sub.githubtest.com" }
+ let(:parent_domain) { "sub.githubtest.com" }
+ subject { described_class.new(:host => domain) }
+ let(:caa_packet_le) do
+ Dnsruby::RR.create("sub.githubtest.com. IN CAA 0 issue \"letsencrypt.org\"")
+ end
+ let(:caa_packet_le_apex) do
+ Dnsruby::RR.create("githubtest.com. IN CAA 0 issue \"digicert.com\"")
+ end
+ let(:caa_packet_other) do
+ Dnsruby::RR.create("#{domain}. IN CAA 0 issue \"digicert.com\"")
+ end
+
+ context "a domain without CAA records" do
+ before(:each) do
+ expect(subject).to receive(:query).with(domain).and_return([])
+ expect(subject).to receive(:query).with(parent_domain).and_return([])
+ end
+
+ it "knows no records exist" do
+ expect(subject).not_to be_records_present
+ end
+
+ it "allows let's encrypt" do
+ expect(subject).to be_lets_encrypt_allowed
+ end
+
+ it "does not encounter an error" do
+ expect(subject).not_to be_errored
+ end
+ end
+
+ context "a domain with LE CAA record" do
+ before(:each) do
+ expect(subject).to receive(:query).with(domain).and_return([caa_packet_le])
+ end
+
+ it "knows records exist" do
+ expect(subject).to be_records_present
+ end
+
+ it "allows let's encrypt" do
+ expect(subject).to be_lets_encrypt_allowed
+ end
+
+ it "does not encounter an error" do
+ expect(subject).not_to be_errored
+ end
+ end
+
+ context "a domain without LE CAA record" do
+ before(:each) do
+ expect(subject).to receive(:query).with(domain).and_return([caa_packet_other])
+ end
+
+ it "knows records exist" do
+ expect(subject).to be_records_present
+ end
+
+ it "doesn't let's encrypt" do
+ expect(subject).not_to be_lets_encrypt_allowed
+ end
+
+ it "does not encounter an error" do
+ expect(subject).not_to be_errored
+ end
+ end
+
+ context "a domain which errors" do
+ before(:each) do
+ expect(subject).to receive(:query).with(domain).and_return([])
+ expect(subject).to receive(:query).with(parent_domain).and_return([])
+ subject.instance_variable_set(:@error, Dnsruby::ServFail.new)
+ end
+
+ it "knows no records exist" do
+ expect(subject).not_to be_records_present
+ end
+
+ it "doesn't allows let's encrypt" do
+ expect(subject).not_to be_lets_encrypt_allowed
+ end
+
+ it "surfaces the error" do
+ expect(subject).to be_errored
+ expect(subject.error.class.name).to eql("Dnsruby::ServFail")
+ end
+ end
+end
diff --git a/spec/github_pages_health_check/cdn_spec.rb b/spec/github_pages_health_check/cdn_spec.rb
new file mode 100644
index 0000000..fb0aa5a
--- /dev/null
+++ b/spec/github_pages_health_check/cdn_spec.rb
@@ -0,0 +1,95 @@
+# frozen_string_literal: true
+
+require "spec_helper"
+require "json"
+require "tempfile"
+require "ipaddr"
+
+RSpec.describe(GitHubPages::HealthCheck::CDN) do
+ subject { described_class.instance }
+
+ it "loads the default config" do
+ path = File.expand_path(subject.path)
+ relative_path = "../../config/cdn-ips.txt"
+ expected = File.expand_path(relative_path, File.dirname(__FILE__))
+ expect(path).to eql(expected)
+ end
+
+ context "with the IP file stubbed" do
+ let(:tempfile) { Tempfile.new("pages-cdn-ips").tap { |f| f.sync = true } }
+ let(:ipaddr_path) { tempfile.path }
+ subject { described_class.send(:new, :path => ipaddr_path) }
+
+ context "no config file" do
+ before { tempfile.unlink }
+
+ it "raises an error" do
+ error = "no implicit conversion of nil into String"
+ expect { subject.send(:ranges) }.to raise_error error
+ end
+ end
+
+ context "parses config" do
+ before { tempfile.write("199.27.128.0/21\n173.245.48.0/20\n2400:cb00::/32") }
+
+ it "has three IPs" do
+ expect(subject.send(:ranges).size).to eql(3)
+ end
+
+ it "loads the IP addresses" do
+ expect(subject.send(:ranges)).to include(IPAddr.new("199.27.128.0/21"))
+ expect(subject.send(:ranges)).to include(IPAddr.new("173.245.48.0/20"))
+ expect(subject.send(:ranges)).to include(IPAddr.new("2400:cb00::/32"))
+ end
+
+ it("controls? 199.27.128.55") do
+ expect(subject.controls_ip?(IPAddr.new("199.27.128.55"))).to be_truthy
+ end
+
+ it("controls? 173.245.48.55") do
+ expect(subject.controls_ip?(IPAddr.new("173.245.48.55"))).to be_truthy
+ end
+
+ it("controls? 2400:cb00:1000:2000:3000:4000:5000:6000") do
+ expect(
+ subject.controls_ip?(
+ IPAddr.new("2400:cb00:1000:2000:3000:4000:5000:6000")
+ )
+ ).to be_truthy
+ end
+
+ it("controls? 200.27.128.55") do
+ expect(subject.controls_ip?(IPAddr.new("200.27.128.55"))).to be_falsey
+ end
+ end
+
+ {
+ "Fastly" => {
+ :valid_ips => ["151.101.32.133", "2a04:4e40:1000:2000:3000:4000:5000:6000"],
+ :invalid_ips => ["108.162.196.20", "2400:cb00:7000:8000:9000:A000:B000:C000"]
+ },
+ "CloudFlare" => {
+ :valid_ips => ["108.162.196.20", "2400:cb00:7000:8000:9000:A000:B000:C000"],
+ :invalid_ips => ["151.101.32.133", "2a04:4e40:1000:2000:3000:4000:5000:6000"]
+ }
+ }.each do |service, ips|
+ context service do
+ it "works as a singleton" do
+ const = "GitHubPages::HealthCheck::#{service}"
+ klass = Kernel.const_get(const).send(:new)
+
+ ips[:valid_ips].each do |ip|
+ expect(klass.controls_ip?(ip)).to eq(true), ip
+ end
+
+ ips[:invalid_ips].each do |ip|
+ expect(klass.controls_ip?(ip)).to eq(false), ip
+ end
+
+ github_ips = GitHubPages::HealthCheck::Domain::CURRENT_IP_ADDRESSES
+ expect(klass.controls_ip?(github_ips.first)).to be(false)
+ end
+ end
+ end
+ end
+end
diff --git a/spec/github_pages_health_check/checkable_spec.rb b/spec/github_pages_health_check/checkable_spec.rb
new file mode 100644
index 0000000..cea7748
--- /dev/null
+++ b/spec/github_pages_health_check/checkable_spec.rb
@@ -0,0 +1,38 @@
+# frozen_string_literal: true
+
+require "spec_helper"
+
+class CheckableHelper < GitHubPages::HealthCheck::Checkable
+ def check!
+ if ENV["OCTOKIT_ACCESS_TOKEN"].to_s.empty?
+ raise GitHubPages::HealthCheck::Errors::MissingAccessTokenError
+ end
+
+ true
+ end
+end
+
+RSpec.describe(CheckableHelper) do
+ context "valid" do
+ it "knows the check is valid" do
+ with_env "OCTOKIT_ACCESS_TOKEN", "1234" do
+ expect(subject.valid?).to eql(true)
+ end
+ end
+ end
+
+ context "invalid" do
+ it "knows the check is invalid" do
+ with_env "OCTOKIT_ACCESS_TOKEN", "" do
+ expect(subject.valid?).to eql(false)
+ end
+ end
+
+ it "knows the reason" do
+ with_env "OCTOKIT_ACCESS_TOKEN", "" do
+ expected = GitHubPages::HealthCheck::Errors::MissingAccessTokenError
+ expect(subject.reason.class).to eql(expected)
+ end
+ end
+ end
+end
diff --git a/spec/github_pages_health_check/domain_spec.rb b/spec/github_pages_health_check/domain_spec.rb
new file mode 100644
index 0000000..878eda1
--- /dev/null
+++ b/spec/github_pages_health_check/domain_spec.rb
@@ -0,0 +1,1268 @@
+# frozen_string_literal: true
+
+require "spec_helper"
+
+RSpec.describe(GitHubPages::HealthCheck::Domain) do
+ let(:domain) { "foo.github.io" }
+ let(:cname) { domain }
+ subject { described_class.new(domain) }
+ let(:cname_packet) do
+ Dnsruby::RR.create("#{domain}. 1000 IN CNAME #{cname}.")
+ end
+ let(:mx_packet) do
+ Dnsruby::RR.create("#{domain}. 1000 IN MX 10 mail.example.com.")
+ end
+ let(:ip) { "127.0.0.1" }
+ let(:a_packet) do
+ Dnsruby::RR.create("#{domain}. 1000 IN A #{ip}")
+ end
+ let(:ip6) { "::1" }
+ let(:aaaa_packet) do
+ Dnsruby::RR.create("#{domain}. 1000 IN AAAA #{ip6}")
+ end
+ let(:caa_domain) { "" }
+ let(:caa_packet) do
+ Dnsruby::RR.create("#{domain}. 1000 IN CAA 0 issue #{caa_domain.inspect}")
+ end
+ let(:soa_packet) do
+ Dnsruby::RR.create("#{domain}. 1000 IN SOA ns.example.com. #{domain.inspect}")
+ end
+ let(:ns_packet) do
+ Dnsruby::RR.create("#{domain}. 1000 IN NS ns.example.com.")
+ end
+
+ context "constructor" do
+ it "can handle bare domains" do
+ expect(subject.host).to eql(domain)
+ end
+
+ context "schemes" do
+ %w(http https ftp).each do |scheme|
+ context scheme do
+ subject do
+ described_class.new("#{scheme}://#{domain}")
+ end
+
+ it "parses the domain" do
+ expect(subject.host).to eql(domain)
+ end
+ end
+ end
+ end
+
+ context "paths" do
+ ["/im-a-path", "/im-a-path/", "/index.html"].each do |path|
+ context path do
+ subject do
+ described_class.new("http://#{domain}/#{path}")
+ end
+
+ it "parses the domain" do
+ expect(subject.host).to eql(domain)
+ end
+ end
+ end
+ end
+
+ context "stripping whitespace" do
+ subject do
+ described_class.new(" #{domain} ")
+ end
+
+ it "parses the domain" do
+ expect(subject.host).to eql(domain)
+ end
+ end
+
+ context "FQDNs" do
+ subject do
+ described_class.new("#{domain}.")
+ end
+
+ it "parses the domain" do
+ expect(subject.host).to eql(domain)
+ end
+ end
+
+ context "invalid domains" do
+ let(:error) { GitHubPages::HealthCheck::Errors::InvalidDomainError }
+
+ context "when given http://@" do
+ let(:domain) { "http://@" }
+
+ it "doesn't blow up" do
+ expect(subject.host).to be_nil
+ expect(subject.reason).to be_a(error)
+ end
+ end
+
+ context "when given //" do
+ let(:domain) { "//" }
+
+ it "doesn't blow up" do
+ expect(subject.host).to be_nil
+ expect(subject.reason).to be_a(error)
+ end
+ end
+ end
+ end
+
+ context "response" do
+ let(:proxy) { "http://proxy.org:5000" }
+ before do
+ GitHubPages::HealthCheck.set_proxy(proxy)
+ end
+ after do
+ GitHubPages::HealthCheck.set_proxy(nil)
+ end
+
+ it "uses a network proxy for outgoing requests" do
+ stub_request(:head, domain).to_return(:status => 200, :headers => {})
+ response = GitHubPages::HealthCheck::Domain.new(domain).send(:response)
+ expect(response.request.options).to include(:proxy => proxy)
+ end
+ end
+
+ context "A records" do
+ before(:each) { allow(subject).to receive(:dns) { [a_packet] } }
+
+ context "old IP addresses" do
+ %w(204.232.175.78 207.97.227.245 192.30.252.153 192.30.252.154).each do |ip_address|
+ context ip_address do
+ let(:ip) { ip_address }
+
+ it "knows it's a deprecated IP" do
+ expect(subject).to be_a_old_ip_address
+ expect(subject).to be_a_deprecated_ip
+ end
+ end
+ end
+
+ %w(185.199.108.153 185.199.109.153 185.199.110.153
+ 185.199.111.153).each do |ip_address|
+ context ip_address do
+ let(:ip) { ip_address }
+
+ it "knows it's not an old IP" do
+ expect(subject).not_to be_a_old_ip_address
+ expect(subject).not_to be_a_deprecated_ip
+ expect(subject).to be_pointed_to_github_pages_ip
+ end
+ end
+ end
+
+ context "a random IP" do
+ let(:ip) { "1.2.3.4" }
+
+ it "knows it's not an old IP" do
+ expect(subject).to_not be_a_old_ip_address
+ end
+ end
+
+ it "doesn't list current IPs as deprecated" do
+ deprecated = GitHubPages::HealthCheck::Domain::LEGACY_IP_ADDRESSES
+ GitHubPages::HealthCheck::Domain::CURRENT_IP_ADDRESSES.each do |ip|
+ expect(deprecated).to_not include(ip)
+ end
+ end
+ end
+
+ it "knows when a domain is an A record" do
+ expect(subject).to be_an_a_record
+ expect(subject).to_not be_a_cname_record
+ end
+
+ it "knows when a domain has an invalid A record" do
+ expect(subject).to be_an_a_record
+ expect(subject).to be_a_valid_domain
+ expect(subject.should_be_a_record?).to be_falsy
+ expect(subject).to be_a_invalid_a_record
+ end
+ end
+
+ context "AAAA records" do
+ before(:each) { allow(subject).to receive(:dns) { [aaaa_packet] } }
+
+ it "knows when a domain is an AAAA record" do
+ expect(subject).to be_an_aaaa_record_present
+ expect(subject).to_not be_a_cname_record
+ end
+
+ it "knows when a domain has an invalid AAAA record" do
+ expect(subject).to be_an_aaaa_record_present
+ expect(subject).to be_a_valid_domain
+ expect(subject.should_be_a_record?).to eq(false)
+ expect(subject).to be_a_invalid_aaaa_record
+ end
+ end
+
+ context "A & AAAA recursive resolutions" do
+ let(:domain) { "domain.com" }
+ let(:cname) { "domain.github.io" }
+ before(:each) do
+ allow(subject).to receive(:dns) {
+ [
+ cname_packet,
+ Dnsruby::RR.create("#{cname}. 1000 IN A #{ip}"),
+ Dnsruby::RR.create("#{cname}. 1000 IN AAAA #{ip6}")
+ ]
+ }
+ end
+
+ it "does not get tricked by recursive resolution" do
+ expect(subject).to_not be_an_aaaa_record_present
+ expect(subject).to_not be_an_a_record_present
+ end
+ end
+
+ context "CNAMEs" do
+ before(:each) { allow(subject).to receive(:dns) { [cname_packet] } }
+
+ it "known when a domain is a CNAME record" do
+ expect(subject).to be_a_cname_record
+ expect(subject).to_not be_an_a_record
+ end
+
+ context "multiple CNAMEs" do
+ let(:cname) { "github.example.com" }
+ before do
+ allow(subject).to receive(:dns) do
+ [
+ cname_packet,
+ Dnsruby::RR.create("#{cname}. 1000 IN CNAME example.github.io."),
+ Dnsruby::RR.create("example.github.io 1000 IN A 192.168.0.1")
+ ]
+ end
+ end
+
+ it "follows the CNAMEs all the way down" do
+ expect(subject.cname.host).to eq("example.github.io")
+ end
+ end
+
+ context "broken CNAMEs" do
+ before do
+ allow(subject).to receive(:dns) do
+ [Dnsruby::RR.create("#{domain}. 300 IN CNAME @.")]
+ end
+ end
+
+ it "handles a broken CNAME gracefully" do
+ expect(subject).to_not be_a_cname
+ expect(subject.cname).to_not be_a_valid_domain
+ end
+ end
+
+ it "returns the cname" do
+ expect(subject.cname.host).to eql(domain)
+ end
+
+ context "with a subdomain" do
+ let(:domain) { "blog.parkermoore.de" }
+
+ it "knows a subdomain is not an apex domain" do
+ expect(subject).to_not be_an_apex_domain
+ end
+ end
+
+ context "with a co.uk subdomain" do
+ let(:domain) { "www.bbc.co.uk" }
+
+ it "knows a subdomain is not an apex domain" do
+ expect(subject).to_not be_an_apex_domain
+ end
+ end
+
+ context "apex records" do
+ ["parkermoore.de", "bbc.co.uk", "techblog.netflix.com"].each do |apex_domain|
+ context "given domain: #{apex_domain} with SOA" do
+ before(:each) { allow(subject).to receive(:dns) { [soa_packet, ns_packet] } }
+
+ let(:domain) { apex_domain }
+
+ it "knows it should be an a record" do
+ expect(subject.should_be_a_record?).to be_truthy
+ end
+ end
+ end
+
+ ["blog.parkermoore.de", "www.bbc.co.uk",
+ "foo.github.io", "pages.github.com"].each do |apex_domain|
+ context "given #{apex_domain}" do
+ let(:domain) { apex_domain }
+
+ it "knows it shouldn't be an a record" do
+ expect(subject.should_be_a_record?).to be_falsy
+ end
+ end
+ end
+
+ ["private.dns.zone"].each do |soa_domain|
+ context "given #{soa_domain}" do
+ before(:each) { allow(subject).to receive(:dns) { [soa_packet, ns_packet] } }
+ let(:domain) { soa_domain }
+
+ it "allows child zones with an SOA to be an Apex" do
+ expect(subject.should_be_a_record?).to eq(true)
+ end
+
+ it "reports whether child zones publish an SOA record" do
+ expect(subject.dns_zone_soa?).to be_truthy
+ end
+ end
+ end
+
+ context "a domain with an MX record" do
+ let(:domain) { "blog.parkermoore.de" }
+ before(:each) do
+ allow(subject).to receive(:dns) { [a_packet, mx_packet] }
+ stub_request(:head, "http://#{domain}")
+ .to_return(:status => 200, :headers => { "Server" => "GitHub.com" })
+ end
+
+ it { is_expected.to be_should_be_a_record }
+ it { is_expected.to be_valid }
+
+ context "pointed to Fastly" do
+ let(:ip) { "151.101.33.147" }
+
+ it { is_expected.to be_a_fastly_ip }
+ end
+ end
+ end
+
+ context "only MX records" do
+ before(:each) { allow(subject).to receive(:dns) { [mx_packet] } }
+ let(:domain) { "pages.invalid" }
+
+ it "must not be valid" do
+ expect(subject).not_to be_valid
+ end
+
+ it "does not think it forwards to a Fastly IP" do
+ expect(subject).not_to be_a_fastly_ip
+ end
+ end
+
+ context "CNAMEs to Pages" do
+ before(:each) { allow(subject).to receive(:dns) { [cname_packet] } }
+
+ ["parkr.github.io", "mattr-.github.com"].each do |cname|
+ context cname do
+ let(:domain) { cname }
+
+ it "can determine a valid GitHub Pages CNAME value" do
+ expect(subject).to be_a_cname_to_github_user_domain
+ end
+ end
+ end
+
+ ["github.com", "ben.balter.com"].each do |cname|
+ context cname do
+ let(:domain) { cname }
+
+ it "can determine a valid GitHub Pages CNAME value" do
+ expect(subject).to_not be_a_cname_to_github_user_domain
+ end
+ end
+ end
+ end
+
+ context "CNAMEs" do
+ let(:domain) { "foo.github.biz" }
+ before(:each) { allow(subject).to receive(:dns) { [cname_packet] } }
+
+ it "detects invalid CNAMEs" do
+ expect(subject).to be_a_valid_domain
+ expect(subject).to_not be_a_github_domain
+ expect(subject).to_not be_an_apex_domain
+ expect(subject).to_not be_a_cname_to_github_user_domain
+ expect(subject).to be_an_invalid_cname
+ end
+
+ context "to pages.github.com" do
+ let(:cname) { "pages.github.com" }
+
+ it "flags CNAMEs to pages.github.com as invalid" do
+ expect(subject).to be_an_invalid_cname
+ end
+
+ it "knows when the domain is CNAME'd to pages.github.com" do
+ expect(subject).to be_a_cname_to_pages_dot_github_dot_com
+ end
+ end
+
+ context "to fastly" do
+ context "github map" do
+ let(:cname) { "github.map.fastly.net" }
+
+ it "flags CNAMEs directly to fastly as invalid" do
+ expect(subject).to be_an_invalid_cname
+ end
+
+ it "knows when the domain is CNAME'd to fastly" do
+ expect(subject).to be_a_cname_to_fastly
+ end
+ end
+
+ context "sni.github map" do
+ let(:cname) { "sni.github.map.fastly.net" }
+
+ it "flags CNAMEs directly to fastly as invalid" do
+ expect(subject).to be_an_invalid_cname
+ end
+
+ it "knows when the domain is CNAME'd to fastly" do
+ expect(subject).to be_a_cname_to_fastly
+ end
+ end
+ end
+
+ context "to other subdomains" do
+ let(:cname) { "foo.github.io" }
+
+ it "knows CNAMEs to user subdomains are valid" do
+ expect(subject.invalid_cname?).to be_falsy
+ end
+
+ it "knows when the domain is CNAME'd to a user domain" do
+ expect(subject).to be_a_cname_to_github_user_domain
+ end
+ end
+ end
+ end
+
+ context "domains" do
+ context "github domains" do
+ let(:domain) { "government.github.com" }
+
+ it "knows if the domain is a github domain" do
+ expect(subject).to be_a_github_domain
+ end
+ end
+
+ context "fastly domain" do
+ let(:domain) { "github.map.fastly.net" }
+
+ it "knows if the domain is a fastly domain" do
+ expect(subject).to be_fastly
+ end
+ end
+
+ context "apex domains" do
+ let(:domain) { "parkermoore.de" }
+
+ it "knows what an apex domain is" do
+ expect(subject).to be_an_apex_domain
+ end
+ end
+ end
+
+ context "cloudflare" do
+ let(:ip) { "108.162.196.20" }
+ before(:each) { allow(subject).to receive(:dns) { [a_packet] } }
+
+ it "knows when the domain is on cloudflare" do
+ expect(subject).to be_a_cloudflare_ip
+ end
+
+ context "a random IP" do
+ let(:ip) { "1.1.1.1" }
+
+ it "know's it's not cloudflare" do
+ expect(subject).to_not be_a_cloudflare_ip
+ end
+ end
+ end
+
+ context "cloudflare IPv6" do
+ let(:ip6) { "2405:b500:7000:8000:9000:A000:B000:C000" }
+ before(:each) { allow(subject).to receive(:dns) { [aaaa_packet] } }
+
+ it "knows when the domain is on cloudflare" do
+ expect(subject).to be_a_cloudflare_ip
+ end
+
+ context "a random IP" do
+ let(:ip6) { "2a04:4e40:1000:2000:3000:4000:5000:6000" }
+
+ it "know's it's not cloudflare" do
+ expect(subject).to_not be_a_cloudflare_ip
+ end
+ end
+ end
+
+ context "GitHub Pages IPs" do
+ context "apex domains" do
+ context "pointed to Pages IP" do
+ let(:domain) { "fontawesome.io" }
+ let(:ip) { "185.199.108.153" }
+ before(:each) { allow(subject).to receive(:dns) { [a_packet] } }
+
+ it "Knows it's a Pages IP" do
+ expect(subject).to be_pointed_to_github_pages_ip
+ end
+ end
+
+ context "not pointed to a pages IP" do
+ let(:domain) { "example.com" }
+
+ it "knows it's not a Pages IP" do
+ expect(subject).to_not be_pointed_to_github_pages_ip
+ end
+ end
+ end
+
+ context "subdomains" do
+ let(:domain) { "pages.github.com" }
+
+ it "Knows it's not a Pages IP" do
+ expect(subject).to_not be_pointed_to_github_pages_ip
+ end
+ end
+ end
+
+ context "GitHub Pages IPv6s" do
+ context "apex domains" do
+ context "pointed to Pages IPv6" do
+ let(:domain) { "myipv6.io" }
+ let(:ip6) { "2606:50C0:8000::153" }
+ before(:each) { allow(subject).to receive(:dns) { [aaaa_packet] } }
+
+ it "Knows it's a Pages IP" do
+ expect(subject).to be_pointed_to_github_pages_ip
+ end
+ end
+
+ context "not pointed to a pages IP" do
+ let(:domain) { "example.com" }
+ let(:ip6) { "::1" }
+
+ it "knows it's not a Pages IP" do
+ expect(subject).to_not be_pointed_to_github_pages_ip
+ end
+ end
+ end
+ end
+
+ context "Pages domains" do
+ ["pages.github.com",
+ "pages.github.io",
+ "pages.github.io."].each do |pages_domain|
+ context pages_domain do
+ let(:domain) { pages_domain }
+
+ it "can detect pages domains" do
+ expect(subject).to be_a_pages_domain
+ end
+ end
+ end
+
+ ["github.com", "google.co.uk"].each do |random_domain|
+ context random_domain do
+ let(:domain) { random_domain }
+
+ it "doesn't detect non-pages domains as a pages domain" do
+ expect(subject).to_not be_a_pages_domain
+ end
+ end
+ end
+ end
+
+ context "served by pages" do
+ let(:domain) { "http://choosealicense.com" }
+ let(:status) { 200 }
+ let(:headers) { {} }
+
+ before do
+ allow(subject).to receive(:dns) { [a_packet] }
+ stub_request(:head, domain)
+ .to_return(:status => status, :headers => headers)
+
+ stub_request(:head, "https://githubuniverse.com/")
+ .to_return(:status => 200, :headers => { :server => "GitHub.com" })
+
+ stub_request(:head, "https://github.com/login")
+ .to_return(:status => 200, :headers => { :server => "GitHub.com" })
+ end
+
+ context "with the Pages server header" do
+ let(:headers) { { :server => "GitHub.com" } }
+
+ it "knows when a domain is served by pages" do
+ expect(subject).to be_served_by_pages
+ end
+
+ context "with a 404" do
+ let(:status) { 404 }
+
+ it "knows when a domain is served by pages even if it returns a 404" do
+ expect(subject).to be_served_by_pages
+ end
+ end
+
+ context "a GitHub domain" do
+ let(:domain) { "https://mac.github.com" }
+
+ it "knows when a GitHub domain is served by pages" do
+ expect(subject).to be_served_by_pages
+ end
+ end
+
+ context "an alternate domain" do
+ let(:domain) { "http://www.githubuniverse.com" }
+ let(:status) { 301 }
+ let(:headers) { { :location => "https://githubuniverse.com" } }
+ before(:each) { allow(subject).to receive(:dns) { [cname_packet] } }
+
+ it "knows about domains that redirect to the primary domain on pages" do
+ expect(subject).to be_served_by_pages
+ end
+ end
+
+ context "a private page" do
+ let(:domain) { "http://private-page.githubapp.com" }
+ let(:status) { 302 }
+ let(:headers) { { :location => "https://github.com/login" } }
+
+ it "considers it valid if it redirects to github.com/login" do
+ expect(subject).to be_served_by_pages
+ end
+ end
+ end
+
+ context "with a request ID" do
+ let(:headers) { { "X-GitHub-Request-Id" => "1234" } }
+
+ it "falls back to the request ID" do
+ expect(subject).to be_served_by_pages
+ end
+ end
+
+ context "a redirect to /" do
+ let(:domain) { "http://getbootstrap.com" }
+
+ before do
+ stub_request(:head, domain)
+ .to_return(:status => 302, :headers => { :location => "/" })
+
+ stub_request(:head, "#{domain}/")
+ .to_return(:status => status, :headers => { :server => "GitHub.com" })
+ end
+
+ it "knows it's served by pages" do
+ expect(subject).to be_served_by_pages
+ end
+ end
+
+ context "an https redirect" do
+ let(:domain) { "management.cio.gov" }
+
+ before do
+ stub_request(:head, "http://#{domain}")
+ .to_return(:status => 302,
+ :headers => { :location => "https://#{domain}" })
+
+ stub_request(:head, "https://#{domain}")
+ .to_return(:status => status, :headers => { :server => "GitHub.com" })
+ end
+
+ it "knows when a domain with a redirect is served by pages" do
+ expect(subject).to be_served_by_pages
+ end
+ end
+
+ context "domains with underscores" do
+ let(:domain) { "this_domain_is_valid.example.com" }
+ let(:cname) { "something.example.com" }
+ let(:headers) { { :server => "GitHub.com" } }
+ before(:each) { allow(subject).to receive(:dns) { [cname_packet] } }
+
+ it "doesn't error out on domains with underscores" do
+ expect(subject).to be_served_by_pages
+ expect(subject).to be_valid
+ end
+ end
+
+ context "private tlds in the public suffix list" do
+ let(:domain) { "githubusercontent.com" }
+ let(:cname) { "something.example.com" }
+ let(:headers) { { :server => "GitHub.com" } }
+ before(:each) { allow(subject).to receive(:dns) { [cname_packet] } }
+
+ it "doesn't error out on private tlds in the public suffix list" do
+ expect(subject).to be_served_by_pages
+ expect(subject).to be_valid
+ end
+ end
+
+ context "domains with unicode encoding" do
+ let(:domain) { "dómain.example.com" }
+ let(:cname) { "sómething.example.com" }
+ let(:headers) { { :server => "GitHub.com" } }
+ before(:each) { allow(subject).to receive(:dns) { [cname_packet] } }
+
+ it "doesn't error out on domains with unicode encoding" do
+ expect(subject).to be_served_by_pages
+ expect(subject).to be_valid
+ end
+ end
+
+ context "domains with punycode encoding" do
+ let(:domain) { "xn--dmain-0ta.example.com" }
+ let(:cname) { "xn--smething-v3a.example.com" }
+ let(:headers) { { :server => "GitHub.com" } }
+ before(:each) { allow(subject).to receive(:dns) { [cname_packet] } }
+
+ it "doesn't error out on domains with punycode encoding" do
+ expect(subject).to be_served_by_pages
+ expect(subject).to be_valid
+ end
+ end
+ end
+
+ context "not served by pages" do
+ let(:domain) { "http://choosealicense.com" }
+ let(:status) { 200 }
+ let(:headers) { {} }
+ let(:not_served_error) do
+ GitHubPages::HealthCheck::Errors::NotServedByPagesError
+ end
+
+ before do
+ stub_request(:head, domain)
+ .to_return(:status => status, :headers => headers)
+ end
+
+ context "a random domain" do
+ let(:domain) { "geogle.com" }
+ let(:ip) { "127.0.0.1" }
+ before(:each) { allow(subject).to receive(:dns) { [a_packet] } }
+
+ it "knows when a domain isn't served by pages" do
+ expect(subject).to_not be_served_by_pages
+ expect(subject.reason).to be_a(not_served_error)
+ msg = "Domain does not resolve to the GitHub Pages server"
+ expect(subject.reason.message).to eql(msg)
+ end
+ end
+
+ context "a non-CNAME" do
+ let(:domain) { "techblog.netflix.com" }
+ let(:cname_error) do
+ GitHubPages::HealthCheck::Errors::InvalidCNAMEError
+ end
+ let(:cname) { "netflix.com" }
+ before(:each) { allow(subject).to receive(:dns) { [cname_packet] } }
+
+ it "returns the error" do
+ expect(subject.valid?).to be_falsy
+ expect(subject.mx_records_present?).to be_falsy
+ expect(subject.should_be_cname_record?).to be_truthy
+ expect(subject.reason).to be_a(cname_error)
+ regex = /not set up with a correct CNAME record/i
+ expect(subject.reason.message).to match(regex)
+ end
+ end
+ end
+
+ context "proxies" do
+ context "by IP" do
+ before(:each) { allow(subject).to receive(:dns) { [a_packet] } }
+
+ context "cloudflare" do
+ let(:ip) { "108.162.196.20" }
+
+ it "knows cloudflare sites are proxied" do
+ expect(subject).to be_proxied
+ end
+ end
+
+ context "a pages IP" do
+ let(:ip) { "185.199.108.153" }
+
+ it "knows a site pointed to a Pages IP isn't proxied" do
+ expect(subject).to_not be_proxied
+ end
+ end
+ end
+
+ context "by cname" do
+ before(:each) { allow(subject).to receive(:dns) { [cname_packet] } }
+
+ context "pointed to pages" do
+ let(:cname) { "foo.github.io" }
+
+ it "knows a site pointed to a Pages domain isn't proxied" do
+ expect(subject).to_not be_proxied
+ end
+ end
+
+ context "pointed to pages.github.com" do
+ let(:cname) { "pages.github.com" }
+
+ it "knows a site CNAMEd to pages.github.com isn't proxied" do
+ expect(subject).to_not be_proxied
+ end
+ end
+
+ context "pointed to Fastly" do
+ let(:cname) { "github.map.fastly.net" }
+ let(:domain) { "foo.github.biz" }
+
+ before do
+ stub_request(:head, "http://#{domain}")
+ .to_return(:status => 200, :headers => { :server => "GitHub.com" })
+ end
+
+ it "knows a site CNAME'd directly to Fastly isn't proxied" do
+ expect(subject).to_not be_proxied
+ end
+ end
+ end
+
+ context "proxying" do
+ let(:headers) { { :server => "GitHub.com" } }
+ let(:status) { 200 }
+ before(:each) do
+ stub_request(:head, domain)
+ .to_return(:status => status, :headers => headers)
+ allow(subject).to receive(:dns) { [a_packet] }
+ end
+
+ context "a site that returns GitHub.com headers" do
+ let(:domain) { "http://management.cio.gov" }
+
+ it "detects proxied sites" do
+ expect(subject).to be_proxied
+ end
+ end
+
+ context "a random site" do
+ let(:domain) { "http://google.com" }
+ let(:headers) { {} }
+
+ it "knows a site not served by pages isn't proxied" do
+ expect(subject).to_not be_proxied
+ end
+ end
+ end
+ end
+
+ context "github domains" do
+ context "pages.github.com" do
+ let(:domain) { "pages.github.com" }
+
+ it "knows when the domain is a github domain" do
+ expect(subject).to be_a_github_domain
+ end
+ end
+
+ context "choosealicense.com" do
+ let(:domain) { "choosealicense.com" }
+
+ it "knows when the domain is not a github domain" do
+ expect(subject).to_not be_a_github_domain
+ end
+ end
+
+ context "benbalter.github.io" do
+ let(:domain) { "benbalter.github.io" }
+
+ it "knows when the domain is not a github domain" do
+ expect(subject).to_not be_a_github_domain
+ end
+ end
+ end
+
+ context "invalid domains" do
+ let(:domain) { "this-domain-does-not-exist-and-should-not-ever-exist.io" }
+
+ it "does not resolve domains that do not exist" do
+ expect(subject.dns).to be_nil
+ end
+
+ context "a valid domain" do
+ let(:domain) { "github.com" }
+
+ it "is valid" do
+ expect(subject).to be_a_valid_domain
+ end
+ end
+
+ context "an invalid domain" do
+ let(:domain) { "github.invalid" }
+
+ it "is invalid" do
+ expect(subject).to_not be_a_valid_domain
+ error = GitHubPages::HealthCheck::Errors::InvalidDomainError
+ expect(subject.reason).to be_a(error)
+ expect(subject.reason.message).to eql("Domain is not a valid domain")
+ end
+ end
+ end
+
+ it "returns the Typhoeus options" do
+ expected = Regexp.escape GitHubPages::HealthCheck::VERSION
+ header = GitHubPages::HealthCheck.typhoeus_options[:headers]["User-Agent"]
+ expect(header).to match(expected)
+ end
+
+ context "dns" do
+ let(:domain) { "pages.github.com" }
+
+ it "retrieves a site's dns record" do
+ expect(subject).to be_dns_resolves
+ expect(subject.dns.first).to be_a(Dnsruby::RR::CNAME)
+ end
+
+ context "with DNS stubbed" do
+ let(:ip) { "1.2.3.4" }
+ before(:each) { allow(subject).to receive(:dns) { [a_packet] } }
+
+ it "knows when the DNS resolves" do
+ expect(subject.dns?).to be_truthy
+ end
+ end
+
+ context "when DNS doesn't resolve" do
+ before(:each) { allow(subject).to receive(:dns) { nil } }
+
+ it "knows when the DNS doesn't resolve" do
+ expect(subject.dns?).to be_falsy
+ end
+
+ it "treats cname as nil if DNS doesn't resolve" do
+ expect(subject.cname).to be_nil
+ end
+ end
+
+ context "an invalid domain" do
+ let(:domain) { "example.invalid" }
+
+ it "knows when a domain has no record" do
+ expect(subject.dns?).to be_falsy
+ end
+ end
+ end
+
+ context "https" do
+ let(:domain) { "pages.github.com" }
+ let(:return_code) { nil }
+
+ before do
+ stub_request(:head, "https://#{domain}/")
+ .to_return(:status => 200, :headers => { :server => "GitHub.com" })
+ allow(subject.send(:https_response)).to receive(:return_code) { return_code }
+ end
+
+ context "a site that supports HTTPS" do
+ let(:return_code) { :ok }
+
+ it "knows it supports https" do
+ expect(subject.https?).to be_truthy
+ end
+
+ it "knows there's no error" do
+ expect(subject.https_error).to be_nil
+ end
+ end
+
+ context "a site that doesn't support HTTPS" do
+ let(:return_code) { :ssl_cacert }
+
+ it "knows it doesn't support https" do
+ expect(subject.https?).to be_falsy
+ end
+
+ it "knows the error reason" do
+ expect(subject.https_error).to eql(:ssl_cacert)
+ end
+
+ it "knows it doesn't enforce https" do
+ expect(subject.enforces_https?).to be_falsy
+ end
+ end
+
+ context "a site that enforces HTTPS" do
+ let(:return_code) { :ok }
+ before do
+ stub_request(:head, "http://#{domain}/")
+ .to_return(:status => 301,
+ :headers => { :Location => "https://#{domain}" })
+ end
+
+ it "knows it supports https" do
+ expect(subject.https?).to be_truthy
+ end
+
+ it "knows it enforces https" do
+ expect(subject.enforces_https?).to be_truthy
+ end
+ end
+
+ context "a site with a relative redirect" do
+ let(:return_code) { :ok }
+ before do
+ stub_request(:head, "http://#{domain}/")
+ .to_return(:status => 301, :headers => { :Location => "/versions" })
+ end
+
+ it "knows it doesn't enforce https" do
+ expect(subject.enforces_https?).to be_falsy
+ end
+ end
+ end
+
+ context "https eligibility" do
+ context "A records pointed to old IPs" do
+ let(:ip) { "192.30.252.153" }
+ before(:each) { allow(subject).to receive(:dns) { [a_packet] } }
+ before(:each) { allow(subject.send(:caa)).to receive(:query) { [a_packet] } }
+
+ it { is_expected.not_to be_https_eligible }
+
+ context "with unicode encoded domain" do
+ let(:domain) { "dómain.example.com" }
+
+ it { is_expected.not_to be_https_eligible }
+ end
+
+ context "with punycode encoded domain" do
+ let(:domain) { "xn--dmain-0ta.example.com" }
+
+ it { is_expected.not_to be_https_eligible }
+ end
+ end
+
+ context "A records pointed to new IPs" do
+ let(:ip) { "185.199.108.153" }
+ before(:each) { allow(subject).to receive(:dns) { [a_packet] } }
+ before(:each) { allow(subject.send(:caa)).to receive(:query) { [a_packet] } }
+
+ it { is_expected.to be_https_eligible }
+
+ context "with underscore domain" do
+ let(:domain) { "foo_bar.com" }
+
+ it { is_expected.not_to be_https_eligible }
+ end
+
+ context "with bad CAA records" do
+ let(:caa_domain) { "digicert.com" }
+ before(:each) { allow(subject.send(:caa)).to receive(:query) { [caa_packet] } }
+
+ it { is_expected.not_to be_https_eligible }
+ end
+
+ context "with good CAA records" do
+ let(:caa_domain) { "letsencrypt.org" }
+ before(:each) { allow(subject.send(:caa)).to receive(:query) { [caa_packet] } }
+
+ it { is_expected.to be_https_eligible }
+ end
+
+ context "with good additional A record" do
+ let(:ip) { "185.199.109.153" }
+
+ it { is_expected.to be_https_eligible }
+ end
+
+ context "with bad additional A record" do
+ let(:ip) { "192.30.252.153" }
+
+ it { is_expected.not_to be_https_eligible }
+ end
+
+ context "with unicode encoded domain" do
+ let(:domain) { "dómain.example.com" }
+
+ it { is_expected.to be_https_eligible }
+
+ context "with bad CAA records" do
+ let(:caa_domain) { "digicert.com" }
+ before(:each) { allow(subject.send(:caa)).to receive(:query) { [caa_packet] } }
+
+ it { is_expected.not_to be_https_eligible }
+ end
+
+ context "with good CAA records" do
+ let(:caa_domain) { "letsencrypt.org" }
+ before(:each) { allow(subject.send(:caa)).to receive(:query) { [caa_packet] } }
+
+ it { is_expected.to be_https_eligible }
+ end
+ end
+
+ context "with punycode encoded domain" do
+ let(:domain) { "xn--dmain-0ta.example.com" }
+
+ it { is_expected.to be_https_eligible }
+
+ context "with bad CAA records" do
+ let(:caa_domain) { "digicert.com" }
+ before(:each) { allow(subject.send(:caa)).to receive(:query) { [caa_packet] } }
+
+ it { is_expected.not_to be_https_eligible }
+ end
+
+ context "with good CAA records" do
+ let(:caa_domain) { "letsencrypt.org" }
+ before(:each) { allow(subject.send(:caa)).to receive(:query) { [caa_packet] } }
+
+ it { is_expected.to be_https_eligible }
+ end
+ end
+ end
+
+ context "AAAA records pointed to current IPs" do
+ let(:ip6) { "2606:50C0:8002::153" }
+ before(:each) { allow(subject).to receive(:dns) { [aaaa_packet] } }
+ before(:each) { allow(subject.send(:caa)).to receive(:query) { [aaaa_packet] } }
+
+ it { is_expected.to be_https_eligible }
+
+ context "with bad CAA records" do
+ let(:caa_domain) { "digicert.com" }
+ before(:each) { allow(subject.send(:caa)).to receive(:query) { [caa_packet] } }
+
+ it { is_expected.not_to be_https_eligible }
+ end
+
+ context "with good CAA records" do
+ let(:caa_domain) { "letsencrypt.org" }
+ before(:each) { allow(subject.send(:caa)).to receive(:query) { [caa_packet] } }
+
+ it { is_expected.to be_https_eligible }
+ end
+
+ context "with good additional A record" do
+ let(:ip6) { "2606:50c0:8003::153" }
+
+ it { is_expected.to be_https_eligible }
+ end
+
+ context "with bad additional A record" do
+ let(:ip6) { "2606:50c0:8003::1111" }
+
+ it { is_expected.not_to be_https_eligible }
+ end
+
+ context "with unicode encoded domain" do
+ let(:domain) { "dómain.example.com" }
+
+ it { is_expected.to be_https_eligible }
+
+ context "with bad CAA records" do
+ let(:caa_domain) { "digicert.com" }
+ before(:each) { allow(subject.send(:caa)).to receive(:query) { [caa_packet] } }
+
+ it { is_expected.not_to be_https_eligible }
+ end
+
+ context "with good CAA records" do
+ let(:caa_domain) { "letsencrypt.org" }
+ before(:each) { allow(subject.send(:caa)).to receive(:query) { [caa_packet] } }
+
+ it { is_expected.to be_https_eligible }
+ end
+ end
+ end
+
+ context "CNAME record pointed to username" do
+ let(:cname) { "foobar.github.io" }
+ before(:each) { allow(subject).to receive(:dns) { [cname_packet] } }
+ before(:each) { allow(subject.send(:caa)).to receive(:query) { [cname_packet] } }
+
+ it { is_expected.to be_https_eligible }
+
+ context "with bad CAA records" do
+ let(:caa_domain) { "digicert.com" }
+ before(:each) { allow(subject.send(:caa)).to receive(:query) { [caa_packet] } }
+
+ it { is_expected.to be_https_eligible }
+ end
+
+ context "with good CAA records" do
+ let(:caa_domain) { "letsencrypt.org" }
+ before(:each) { allow(subject.send(:caa)).to receive(:query) { [caa_packet] } }
+
+ it { is_expected.to be_https_eligible }
+ end
+
+ context "with unicode encoded domain" do
+ let(:domain) { "dómain.example.com" }
+
+ it { is_expected.to be_https_eligible }
+
+ context "with bad CAA records" do
+ let(:caa_domain) { "digicert.com" }
+ before(:each) { allow(subject.send(:caa)).to receive(:query) { [caa_packet] } }
+
+ it { is_expected.to be_https_eligible }
+ end
+
+ context "with good CAA records" do
+ let(:caa_domain) { "letsencrypt.org" }
+ before(:each) { allow(subject.send(:caa)).to receive(:query) { [caa_packet] } }
+
+ it { is_expected.to be_https_eligible }
+ end
+ end
+
+ context "with punycode encoded domain" do
+ let(:domain) { "xn--dmain-0ta.example.com" }
+
+ it { is_expected.to be_https_eligible }
+
+ context "with bad CAA records" do
+ let(:caa_domain) { "digicert.com" }
+ before(:each) { allow(subject.send(:caa)).to receive(:query) { [caa_packet] } }
+
+ it { is_expected.to be_https_eligible }
+ end
+
+ context "with good CAA records" do
+ let(:caa_domain) { "letsencrypt.org" }
+ before(:each) { allow(subject.send(:caa)).to receive(:query) { [caa_packet] } }
+
+ it { is_expected.to be_https_eligible }
+ end
+ end
+ end
+
+ context "CNAME record pointed elsewhere" do
+ let(:cname) { "jinglebells.com" }
+ before(:each) { allow(subject).to receive(:dns) { [cname_packet] } }
+ before(:each) { allow(subject.send(:caa)).to receive(:query) { [cname_packet] } }
+
+ it { is_expected.not_to be_https_eligible }
+
+ context "with bad CAA records" do
+ let(:caa_domain) { "digicert.com" }
+ before(:each) { allow(subject.send(:caa)).to receive(:query) { [caa_packet] } }
+
+ it { is_expected.not_to be_https_eligible }
+ end
+
+ context "with good CAA records" do
+ let(:caa_domain) { "letsencrypt.org" }
+ before(:each) { allow(subject.send(:caa)).to receive(:query) { [caa_packet] } }
+
+ it { is_expected.not_to be_https_eligible }
+ end
+
+ context "with unicode encoded domain" do
+ let(:domain) { "dómain.example.com" }
+
+ it { is_expected.not_to be_https_eligible }
+ end
+
+ context "with punycode encoded domain" do
+ let(:domain) { "xn--dmain-0ta.example.com" }
+
+ it { is_expected.not_to be_https_eligible }
+ end
+ end
+ end
+end
diff --git a/spec/github_pages_health_check/error_spec.rb b/spec/github_pages_health_check/error_spec.rb
new file mode 100644
index 0000000..bc8da15
--- /dev/null
+++ b/spec/github_pages_health_check/error_spec.rb
@@ -0,0 +1,64 @@
+# frozen_string_literal: true
+
+require "spec_helper"
+
+RSpec.describe(GitHubPages::HealthCheck::Error) do
+ GitHubPages::HealthCheck::Errors.all.each do |klass|
+ next if klass::LOCAL_ONLY
+
+ context "The #{klass.name.split("::").last} error" do
+ let(:domain) { GitHubPages::HealthCheck::Domain.new("example.com") }
+ subject { klass.new(:domain => domain) }
+
+ it "has a message" do
+ expect(subject.message).to_not be_empty
+ end
+
+ it "has a documentation url" do
+ expect(klass::DOCUMENTATION_PATH).to_not be_nil
+ expect(klass::DOCUMENTATION_PATH).to_not be_empty
+ default = "/categories/github-pages-basics/"
+ expect(klass::DOCUMENTATION_PATH).to_not eql(default)
+ expect(subject.send(:documentation_url)).to_not be_nil
+ end
+
+ it "the documentation path has a trailing slash" do
+ expect(klass::DOCUMENTATION_PATH).to match(%r{/$})
+ end
+ end
+ end
+
+ context "with a repository" do
+ let(:nwo) { "github/pages.github.com" }
+ let(:repo) { GitHubPages::HealthCheck::Repository.new(nwo) }
+ subject { described_class.new(:repository => repo) }
+
+ it "knows the username" do
+ expect(subject.send(:username)).to eql("github")
+ end
+ end
+
+ context "without a repository" do
+ it "has a placeholder username" do
+ expect(subject.send(:username)).to eql("[YOUR USERNAME]")
+ end
+ end
+
+ it "builds the documentation URL" do
+ url = "https://help.github.com/categories/github-pages-basics/"
+ expect(subject.send(:documentation_url)).to eql(url)
+ end
+
+ it "builds the more info string" do
+ msg = "For more information, " \
+ "see https://help.github.com/categories/github-pages-basics/."
+ expect(subject.send(:more_info)).to eql(msg)
+ end
+
+ it "returns the message with URL" do
+ msg = "Something's wrong with your GitHub Pages site. " \
+ "For more information, " \
+ "see https://help.github.com/categories/github-pages-basics/."
+ expect(subject.message_with_url).to eql(msg)
+ end
+end
diff --git a/spec/github_pages_health_check/errors_spec.rb b/spec/github_pages_health_check/errors_spec.rb
new file mode 100644
index 0000000..bd13037
--- /dev/null
+++ b/spec/github_pages_health_check/errors_spec.rb
@@ -0,0 +1,9 @@
+# frozen_string_literal: true
+
+require "spec_helper"
+
+RSpec.describe(GitHubPages::HealthCheck::Errors) do
+ it "returns the errors" do
+ expect(GitHubPages::HealthCheck::Errors.all.count).to eql(10)
+ end
+end
diff --git a/spec/github_pages_health_check/redundant_check_spec.rb b/spec/github_pages_health_check/redundant_check_spec.rb
new file mode 100644
index 0000000..e5526d2
--- /dev/null
+++ b/spec/github_pages_health_check/redundant_check_spec.rb
@@ -0,0 +1,20 @@
+# frozen_string_literal: true
+
+require "spec_helper"
+
+RSpec.describe(GitHubPages::HealthCheck::RedundantCheck) do
+ let(:domain) { "www.parkermoore.de" }
+ subject { described_class.new(domain) }
+ before(:each) do
+ stub_request(:head, "http://#{domain}/")
+ .to_return(:status => 200, :body => "", :headers => { "Server" => "GitHub.com" })
+ end
+
+ it { is_expected.to be_valid }
+ it { is_expected.to be_https_eligible }
+
+ it "has a link to the check which was most valid" do
+ expect(subject.check).not_to be_nil
+ expect(subject.check).to be_valid
+ end
+end
diff --git a/spec/github_pages_health_check/repository_spec.rb b/spec/github_pages_health_check/repository_spec.rb
new file mode 100644
index 0000000..a0b98a0
--- /dev/null
+++ b/spec/github_pages_health_check/repository_spec.rb
@@ -0,0 +1,171 @@
+# frozen_string_literal: true
+
+require "spec_helper"
+
+RSpec.describe(GitHubPages::HealthCheck::Repository) do
+ let(:owner) { "github" }
+ let(:repo_name) { "pages.github.com" }
+ let(:repo) { "#{owner}/#{repo_name}" }
+ let(:access_token) { nil }
+ subject { described_class.new(repo, :access_token => access_token) }
+ before(:each) do
+ stub_request(:head, "https://#{repo_name}/")
+ .to_return(:status => 200, :body => "", :headers => { "Server" => "GitHub.com" })
+ end
+
+ context "constructor" do
+ context "an invalid repository" do
+ it "should raise an error" do
+ expected = GitHubPages::HealthCheck::Errors::InvalidRepositoryError
+ expect { described_class.new("example.com") }.to raise_error(expected)
+ end
+ end
+
+ it "should extract the owner" do
+ expect(subject.owner).to eql(owner)
+ end
+
+ it "should extract the repo name" do
+ expect(subject.name).to eql(repo_name)
+ end
+
+ it "should build the name with owner" do
+ expect(subject.name_with_owner).to eql(repo)
+ end
+
+ context "with an access token" do
+ let(:access_token) { "1234" }
+
+ it "should parse the access token, when explicitly passed" do
+ actual_token = subject.instance_variable_get("@access_token")
+ expect(actual_token).to eql(access_token)
+ end
+ end
+
+ it "should parse the access token when passed as an env var" do
+ with_env "OCTOKIT_ACCESS_TOKEN", "1234" do
+ actual_token = subject.instance_variable_get("@access_token")
+ expect(actual_token).to eql("1234")
+ end
+ end
+ end
+
+ %w(error success).each do |type|
+ context "a build that was a(n) #{type}" do
+ let(:access_token) { "1234" }
+ let(:fixture) { File.read(fixture_path("build_#{type}.json")) }
+ let(:url) { "https://api.github.com/repos/#{repo}/pages/builds/latest" }
+
+ before do
+ stub_request(:get, url)
+ .to_return(:status => 200,
+ :body => fixture,
+ :headers => { "Content-Type" => "application/json" })
+ end
+
+ if type == "error"
+ it "fails the check" do
+ build_error = GitHubPages::HealthCheck::Errors::BuildError
+ expect { subject.check! }.to raise_error(build_error)
+ end
+
+ it "returns the build error" do
+ expect(subject.build_error).to eql("Some message")
+ end
+
+ it "knows the site wasn't built" do
+ expect(subject.built?).to be_falsy
+ end
+ else
+ it "passes the check" do
+ expect(subject.check!).to be_truthy
+ end
+
+ it "returns no build error" do
+ expect(subject.build_error).to be_nil
+ end
+
+ it "knows the site was built" do
+ expect(subject.built?).to be_truthy
+ end
+ end
+
+ it "returns the build info" do
+ expected = "351391cdcb88ffae71ec3028c91f375a8036a26b"
+ expect(subject.last_build["commit"]).to eql(expected)
+ end
+
+ it "knows the build duration" do
+ expect(subject.build_duration).to eql(2104)
+ end
+
+ it "knows when it was last built" do
+ expect(subject.last_built.to_s).to match(/2014-02-10/)
+ end
+ end
+ end
+
+ context "the client" do
+ context "with an access token" do
+ let(:access_token) { "1234" }
+
+ it "inits the client" do
+ expect(subject.send(:client)).to be_a(Octokit::Client)
+ end
+
+ it "passes the token" do
+ expect(subject.send(:client).access_token).to eql("1234")
+ end
+ end
+
+ context "without an access token" do
+ it "raises an error" do
+ expected = GitHubPages::HealthCheck::Errors::MissingAccessTokenError
+ expect { subject.send(:client) }.to raise_error(expected)
+ end
+ end
+ end
+
+ context "pages info" do
+ let(:access_token) { "1234" }
+ let(:fixture) { File.read(fixture_path("pages_info.json")) }
+ let(:url) { "https://api.github.com/repos/#{repo}/pages" }
+
+ before do
+ stub_request(:get, url)
+ .to_return(:status => 200,
+ :body => fixture,
+ :headers => { "Content-Type" => "application/json" })
+ end
+
+ it "returns the pages info" do
+ expect(subject.send(:pages_info).status).to eql("built")
+ end
+
+ it "knows the CNAME" do
+ expect(subject.send(:cname)).to eql("pages.github.com")
+ end
+
+ it "returns the domain" do
+ expect(subject.domain.class).to eql(GitHubPages::HealthCheck::Domain)
+ expect(subject.domain.host).to eql("pages.github.com")
+ end
+
+ context "without a CNAME" do
+ let(:access_token) { "1234" }
+ let(:fixture) { File.read(fixture_path("pages_info_no_cname.json")) }
+ let(:url) { "https://api.github.com/repos/#{repo}/pages" }
+
+ before do
+ stub_request(:get, url)
+ .to_return(:status => 200,
+ :body => fixture,
+ :headers => { "Content-Type" => "application/json" })
+ end
+
+ it "doesn't try to build the domain" do
+ expect(subject.domain).to be_nil
+ end
+ end
+ end
+end
diff --git a/spec/github_pages_health_check/resolver_spec.rb b/spec/github_pages_health_check/resolver_spec.rb
new file mode 100644
index 0000000..966c673
--- /dev/null
+++ b/spec/github_pages_health_check/resolver_spec.rb
@@ -0,0 +1,40 @@
+# frozen_string_literal: true
+
+require "spec_helper"
+
+RSpec.describe(GitHubPages::HealthCheck::Resolver) do
+ let(:domain) { "example.com" }
+ let(:nameservers) { :default }
+ subject { described_class.new(domain, :nameservers => nameservers) }
+
+ context "default" do
+ it "uses the default resolver" do
+ expect(described_class.default_resolver).to \
+ receive(:query).with(domain, Dnsruby::Types::A).and_call_original
+ subject.query(Dnsruby::Types::A)
+ end
+ end
+
+ context "authoritative" do
+ let(:nameservers) { :authoritative }
+
+ it "uses an authoritative resolver" do
+ expect(described_class.default_resolver).to \
+ receive(:query).with(domain, Dnsruby::Types::NS).and_call_original
+ expect(subject.send(:resolver)).to \
+ receive(:query).with(domain, Dnsruby::Types::A).and_call_original
+ subject.query(Dnsruby::Types::A)
+ end
+ end
+
+ context "custom" do
+ let(:nameservers) { ["8.8.8.8", "8.8.4.4"] }
+
+ it "uses the custom resolver" do
+ expect(subject.send(:resolver).config.nameserver).to eq(nameservers)
+ expect(subject.send(:resolver)).to \
+ receive(:query).with(domain, Dnsruby::Types::A).and_call_original
+ subject.query(Dnsruby::Types::A)
+ end
+ end
+end
diff --git a/spec/github_pages_health_check/site_spec.rb b/spec/github_pages_health_check/site_spec.rb
new file mode 100644
index 0000000..bce7476
--- /dev/null
+++ b/spec/github_pages_health_check/site_spec.rb
@@ -0,0 +1,128 @@
+# frozen_string_literal: true
+
+require "spec_helper"
+
+RSpec.describe(GitHubPages::HealthCheck::Site) do
+ let(:api_base) { "https://api.github.com" }
+ let(:repo) { "github/page.github.com" }
+ let(:domain) { "pages.github.com" }
+
+ before do
+ stub_request(:head, "https://#{domain}/")
+ .to_return(:status => 200, :headers => { :server => "GitHub.com" })
+
+ stub_request(:head, "http://#{domain}/")
+ .to_return(:status => 200, :headers => { :server => "GitHub.com" })
+ end
+
+ %w(domain repo).each do |init_type|
+ context "initialized with a #{init_type}" do
+ subject do
+ return described_class.new(domain) if init_type == "domain"
+
+ subject = nil
+ with_env "OCTOKIT_ACCESS_TOKEN", "1234" do
+ subject = described_class.new repo
+ end
+ subject
+ end
+
+ context "with a cname" do
+ before do
+ if init_type == "repo"
+ fixture = File.read(fixture_path("pages_info.json"))
+ stub_request(:get, "#{api_base}/repos/#{repo}/pages")
+ .to_return(:status => 200,
+ :body => fixture,
+ :headers => { "Content-Type" => "application/json" })
+
+ fixture = File.read(fixture_path("build_success.json"))
+ stub_request(:get, "#{api_base}/repos/#{repo}/pages/builds/latest")
+ .to_return(:status => 200,
+ :body => fixture,
+ :headers => { "Content-Type" => "application/json" })
+ end
+ end
+
+ it "knows the domain" do
+ expect(subject.domain).to be_a(GitHubPages::HealthCheck::Domain)
+ expect(subject.domain.host).to eql("pages.github.com")
+ end
+
+ it "builds the hash" do
+ expect(subject.to_hash[:host]).to eql("pages.github.com")
+ end
+
+ if init_type == "repo"
+ it "knows the repository" do
+ klass = GitHubPages::HealthCheck::Repository
+ expect(subject.repository).to be_a(klass)
+ expect(subject.repository.name_with_owner).to eql(repo)
+ end
+ else
+ it "knows it doesn't know the repository" do
+ expect(subject.repository).to be_nil
+ end
+ end
+
+ context "json" do
+ let(:json) { JSON.parse subject.to_json }
+
+ it "returns valid json" do
+ expect(json.delete("uri")).to eql("https://pages.github.com/")
+ end
+ end
+
+ context "hash" do
+ let(:valid_values) { [true, false, nil, :default] }
+
+ it "returns a valid values" do
+ hash = subject.to_hash
+ expect(hash.delete(:host)).to eql(domain)
+ expect(hash.delete(:uri)).to eql("https://#{domain}/")
+
+ if init_type == "repo"
+ expect(hash.delete(:name_with_owner)).to eql(repo)
+ expect(hash.delete(:last_built).to_s).to match(/2014-02-10/)
+ expect(hash.delete(:build_duration)).to eql(2104)
+ end
+
+ hash.each do |key, value|
+ msg = "Expected #{key} to be one of #{valid_values}"
+ expect(valid_values).to include(value), msg
+ end
+ end
+ end
+ end
+
+ context "with no cname" do
+ before do
+ if init_type == "repo"
+ fixture = File.read(fixture_path("pages_info_no_cname.json"))
+ stub_request(:get, "#{api_base}/repos/#{repo}/pages")
+ .to_return(:status => 200,
+ :body => fixture,
+ :headers => { "Content-Type" => "application/json" })
+
+ fixture = File.read(fixture_path("build_success.json"))
+ url = "#{api_base}/repos/#{repo}/pages/builds/latest"
+ stub_request(:get, url)
+ .to_return(:status => 200,
+ :body => fixture,
+ :headers => { "Content-Type" => "application/json" })
+ end
+ end
+
+ if init_type == "repo"
+ it "knows it doesn't know the domain" do
+ expect(subject.domain).to be_nil
+ end
+
+ it "doesnt err out when it checks" do
+ expect(subject.check!).to be_truthy
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/spec/github_pages_health_check_spec.rb b/spec/github_pages_health_check_spec.rb
new file mode 100644
index 0000000..dc1e90f
--- /dev/null
+++ b/spec/github_pages_health_check_spec.rb
@@ -0,0 +1,23 @@
+# frozen_string_literal: true
+
+require "spec_helper"
+
+RSpec.describe(GitHubPages::HealthCheck) do
+ let(:domain) { "pages.github.com" }
+ before(:each) do
+ stub_request(:head, "https://#{domain}/")
+ .to_return(:status => 200, :body => "", :headers => { "Server" => "GitHub.com" })
+ end
+
+ it "checks" do
+ check = GitHubPages::HealthCheck.check(domain)
+ expect(check.class).to eql(GitHubPages::HealthCheck::Site)
+ expect(check.domain.host).to eql(domain)
+ end
+
+ it "sets a network proxy url" do
+ expect(GitHubPages::HealthCheck.typhoeus_options).to include(:proxy => nil)
+ GitHubPages::HealthCheck.set_proxy("http://proxy.org")
+ expect(GitHubPages::HealthCheck.typhoeus_options).to include(:proxy => "http://proxy.org")
+ end
+end
diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb
new file mode 100644
index 0000000..3609286
--- /dev/null
+++ b/spec/spec_helper.rb
@@ -0,0 +1,28 @@
+# frozen_string_literal: true
+
+require "bundler/setup"
+require "webmock/rspec"
+require "pry-byebug"
+require_relative "../lib/github-pages-health-check"
+
+WebMock.disable_net_connect!
+
+RSpec.configure do |config|
+ config.raise_errors_for_deprecations!
+ config.disable_monkey_patching!
+ config.example_status_persistence_file_path = "spec/examples.txt"
+ config.default_formatter = "doc" if config.files_to_run.one?
+ config.order = :random
+ Kernel.srand config.seed
+end
+
+def with_env(key, value)
+ old_env = ENV[key]
+ ENV[key] = value
+ yield
+ ENV[key] = old_env
+end
+
+def fixture_path(fixture = "")
+ File.expand_path "./fixtures/#{fixture}", File.dirname(__FILE__)
+end
Debdiff
[The following lists of changes regard files as different if they have different names, permissions or owners.]
Files in second set of .debs but not in first
-rw-r--r-- root/root /usr/share/rubygems-integration/all/specifications/github-pages-health-check-1.18.1.gemspec
Files in first set of .debs but not in second
-rw-r--r-- root/root /usr/share/rubygems-integration/all/specifications/github-pages-health-check-1.16.1.gemspec
Control files: lines which differ (wdiff format)
Ruby-Versions: all