# -*- coding: utf-8 -*-
require 'helper'
require 'ipaddr'

class TestDomainName < Test::Unit::TestCase
  should "raise ArgumentError if hostname starts with a dot" do
    [
      # Leading dot.
      '.com',
      '.example',
      '.example.com',
      '.example.example',
    ].each { |hostname|
      assert_raises(ArgumentError) { DomainName.new(hostname) }
    }
  end

  should "accept a String-alike for initialization" do
    Object.new.tap { |obj|
      def obj.to_str
        "Example.org"
      end
      assert_equal "example.org", DomainName.new(obj).hostname
    }

    Object.new.tap { |obj|
      def obj.to_str
        123
      end
      assert_raises(TypeError) { DomainName.new(obj) }
    }

    Object.new.tap { |obj|
      assert_raises(TypeError) { DomainName.new(obj) }
    }
  end

  should "parse canonical domain names correctly" do
    [
      # Mixed case.
      ['COM', nil, false, 'com', true],
      ['example.COM', 'example.com', true, 'com', true],
      ['WwW.example.COM', 'example.com', true, 'com', true],
      # Unlisted TLD.
      ['example', 'example', false, 'example', false],
      ['example.example', 'example.example', false, 'example', false],
      ['b.example.example', 'example.example', false, 'example', false],
      ['a.b.example.example', 'example.example', false, 'example', false],
      # Listed, but non-Internet, TLD.
      ['local', 'local', false, 'local', false],
      ['example.local', 'example.local', false, 'local', false],
      ['b.example.local', 'example.local', false, 'local', false],
      ['a.b.example.local', 'example.local', false, 'local', false],
      # TLD with only 1 rule.
      ['biz', nil, false, 'biz', true],
      ['domain.biz', 'domain.biz', true, 'biz', true],
      ['b.domain.biz', 'domain.biz', true, 'biz', true],
      ['a.b.domain.biz', 'domain.biz', true, 'biz', true],
      # TLD with some 2-level rules.
      ['com', nil, false, 'com', true],
      ['example.com', 'example.com', true, 'com', true],
      ['b.example.com', 'example.com', true, 'com', true],
      ['a.b.example.com', 'example.com', true, 'com', true],
      ['uk.com', nil, false, 'com', true],
      ['example.uk.com', 'example.uk.com', true, 'com', true],
      ['b.example.uk.com', 'example.uk.com', true, 'com', true],
      ['a.b.example.uk.com', 'example.uk.com', true, 'com', true],
      ['test.ac', 'test.ac', true, 'ac', true],
      # TLD with only 1 (wildcard) rule.
      ['cy', nil, false, 'cy', true],
      ['c.cy', nil, false, 'cy', true],
      ['b.c.cy', 'b.c.cy', true, 'cy', true],
      ['a.b.c.cy', 'b.c.cy', true, 'cy', true],
      # More complex TLD.
      ['jp', nil, false, 'jp', true],
      ['test.jp', 'test.jp', true, 'jp', true],
      ['www.test.jp', 'test.jp', true, 'jp', true],
      ['ac.jp', nil, false, 'jp', true],
      ['test.ac.jp', 'test.ac.jp', true, 'jp', true],
      ['www.test.ac.jp', 'test.ac.jp', true, 'jp', true],
      ['kyoto.jp', nil, false, 'jp', true],
      ['test.kyoto.jp', 'test.kyoto.jp', true, 'jp', true],
      ['ide.kyoto.jp', nil, false, 'jp', true],
      ['b.ide.kyoto.jp', 'b.ide.kyoto.jp', true, 'jp', true],
      ['a.b.ide.kyoto.jp', 'b.ide.kyoto.jp', true, 'jp', true],
      ['c.kobe.jp', nil, false, 'jp', true],
      ['b.c.kobe.jp', 'b.c.kobe.jp', true, 'jp', true],
      ['a.b.c.kobe.jp', 'b.c.kobe.jp', true, 'jp', true],
      ['city.kobe.jp', 'city.kobe.jp', true, 'jp', true],
      ['www.city.kobe.jp', 'city.kobe.jp', true, 'jp', true],
      # TLD with a wildcard rule and exceptions.
      ['ck', nil, false, 'ck', true],
      ['test.ck', nil, false, 'ck', true],
      ['b.test.ck', 'b.test.ck', true, 'ck', true],
      ['a.b.test.ck', 'b.test.ck', true, 'ck', true],
      ['www.ck', 'www.ck', true, 'ck', true],
      ['www.www.ck', 'www.ck', true, 'ck', true],
      # US K12.
      ['us', nil, false, 'us', true],
      ['test.us', 'test.us', true, 'us', true],
      ['www.test.us', 'test.us', true, 'us', true],
      ['ak.us', nil, false, 'us', true],
      ['test.ak.us', 'test.ak.us', true, 'us', true],
      ['www.test.ak.us', 'test.ak.us', true, 'us', true],
      ['k12.ak.us', nil, false, 'us', true],
      ['test.k12.ak.us', 'test.k12.ak.us', true, 'us', true],
      ['www.test.k12.ak.us', 'test.k12.ak.us', true, 'us', true],
      # IDN labels. (modified; currently DomainName always converts U-labels to A-labels)
      ['食狮.com.cn', 'xn--85x722f.com.cn', true, 'cn', true],
      ['食狮.公司.cn', 'xn--85x722f.xn--55qx5d.cn', true, 'cn', true],
      ['www.食狮.公司.cn', 'xn--85x722f.xn--55qx5d.cn', true, 'cn', true],
      ['shishi.公司.cn', 'shishi.xn--55qx5d.cn', true, 'cn', true],
      ['公司.cn', nil, false, 'cn', true],
      ['食狮.中国', 'xn--85x722f.xn--fiqs8s', true, 'xn--fiqs8s', true],
      ['www.食狮.中国', 'xn--85x722f.xn--fiqs8s', true, 'xn--fiqs8s', true],
      ['shishi.中国', 'shishi.xn--fiqs8s', true, 'xn--fiqs8s', true],
      ['中国', nil, false, 'xn--fiqs8s', true],
      # Same as above, but punycoded.
      ['xn--85x722f.com.cn', 'xn--85x722f.com.cn', true, 'cn', true],
      ['xn--85x722f.xn--55qx5d.cn', 'xn--85x722f.xn--55qx5d.cn', true, 'cn', true],
      ['www.xn--85x722f.xn--55qx5d.cn', 'xn--85x722f.xn--55qx5d.cn', true, 'cn', true],
      ['shishi.xn--55qx5d.cn', 'shishi.xn--55qx5d.cn', true, 'cn', true],
      ['xn--55qx5d.cn', nil, false, 'cn', true],
      ['xn--85x722f.xn--fiqs8s', 'xn--85x722f.xn--fiqs8s', true, 'xn--fiqs8s', true],
      ['www.xn--85x722f.xn--fiqs8s', 'xn--85x722f.xn--fiqs8s', true, 'xn--fiqs8s', true],
      ['shishi.xn--fiqs8s', 'shishi.xn--fiqs8s', true, 'xn--fiqs8s', true],
      ['xn--fiqs8s', nil, false, 'xn--fiqs8s', true],
    ].each { |hostname, domain, canonical, tld, canonical_tld|
      dn = DomainName.new(hostname)
      assert_equal(domain, dn.domain, hostname + ':domain')
      assert_equal(canonical, dn.canonical?, hostname + ':canoninal?')
      assert_equal(tld, dn.tld, hostname + ':tld')
      assert_equal(canonical_tld, dn.canonical_tld?, hostname + ':canoninal_tld?')
    }
  end

  should "compare hostnames correctly" do
    [
      ["foo.com", "abc.foo.com", 1],
      ["COM", "abc.foo.com", 1],
      ["abc.def.foo.com", "foo.com", -1],
      ["abc.def.foo.com", "ABC.def.FOO.com", 0],
      ["abc.def.foo.com", "bar.com", nil],
    ].each { |x, y, v|
      dx, dy = DomainName(x), DomainName(y)
      [
        [dx, y, v],
        [dx, dy, v],
        [dy, x, v ? -v : v],
        [dy, dx, v ? -v : v],
      ].each { |a, b, expected|
        assert_equal expected, a <=> b
        case expected
        when 1
          assert_equal(true,  a >  b)
          assert_equal(true,  a >= b)
          assert_equal(false, a == b)
          assert_equal(false, a <= b)
          assert_equal(false, a <  b)
        when -1
          assert_equal(true,  a <  b)
          assert_equal(true,  a <= b)
          assert_equal(false, a == b)
          assert_equal(false, a >= b)
          assert_equal(false, a >  b)
        when 0
          assert_equal(false, a <  b)
          assert_equal(true,  a <= b)
          assert_equal(true,  a == b)
          assert_equal(true,  a >= b)
          assert_equal(false, a >  b)
        when nil
          assert_equal(nil,   a <  b)
          assert_equal(nil,   a <= b)
          assert_equal(false, a == b)
          assert_equal(nil,   a >= b)
          assert_equal(nil,   a >  b)
        end
      }
    }
  end

  should "check cookie domain correctly" do
    {
      'com' => [
        ['com', false],
        ['example.com', false],
        ['foo.example.com', false],
        ['bar.foo.example.com', false],
      ],

      'example.com' => [
        ['com', false],
        ['example.com', true],
        ['foo.example.com', false],
        ['bar.foo.example.com', false],
      ],

      'foo.example.com' => [
        ['com', false],
        ['example.com', true],
        ['foo.example.com', true],
        ['foo.Example.com', true],
        ['bar.foo.example.com', false],
        ['bar.Foo.Example.com', false],
      ],

      'b.sapporo.jp' => [
        ['jp', false],
        ['sapporo.jp', false],
        ['b.sapporo.jp', false],
        ['a.b.sapporo.jp', false],
      ],

      'b.c.sapporo.jp' => [
        ['jp', false],
        ['sapporo.jp', false],
        ['c.sapporo.jp', false],
        ['b.c.sapporo.jp', true],
        ['a.b.c.sapporo.jp', false],
      ],

      'b.c.d.sapporo.jp' => [
        ['jp', false],
        ['sapporo.jp', false],
        ['d.sapporo.jp', false],
        ['c.d.sapporo.jp', true],
        ['b.c.d.sapporo.jp', true],
        ['a.b.c.d.sapporo.jp', false],
      ],

      'city.sapporo.jp' => [
        ['jp', false],
        ['sapporo.jp', false],
        ['city.sapporo.jp', true],
        ['a.city.sapporo.jp', false],
      ],

      'b.city.sapporo.jp' => [
        ['jp', false],
        ['sapporo.jp', false],
        ['city.sapporo.jp', true],
        ['b.city.sapporo.jp', true],
        ['a.b.city.sapporo.jp', false],
      ],
    }.each_pair { |host, pairs|
      dn = DomainName(host)
      assert_equal(true, dn.cookie_domain?(host.upcase, true),     dn.to_s)
      assert_equal(true, dn.cookie_domain?(host.downcase, true),   dn.to_s)
      assert_equal(false, dn.cookie_domain?("www." << host, true), dn.to_s)
      pairs.each { |domain, expected|
        assert_equal(expected, dn.cookie_domain?(domain),             "%s - %s" % [dn.to_s, domain])
        assert_equal(expected, dn.cookie_domain?(DomainName(domain)), "%s - %s" % [dn.to_s, domain])
      }
    }
  end

  should "parse IPv4 addresseses" do
    a = '192.168.10.20'
    dn = DomainName(a)
    assert_equal(a, dn.hostname)
    assert_equal(true, dn.ipaddr?)
    assert_equal(IPAddr.new(a), dn.ipaddr)
    assert_equal(true, dn.cookie_domain?(a))
    assert_equal(true, dn.cookie_domain?(a, true))
    assert_equal(true, dn.cookie_domain?(dn))
    assert_equal(true, dn.cookie_domain?(dn, true))
    assert_equal(false, dn.cookie_domain?('168.10.20'))
    assert_equal(false, dn.cookie_domain?('20'))
    assert_equal(nil, dn.superdomain)
  end

  should "parse IPv6 addresseses" do
    a = '2001:200:dff:fff1:216:3eff:feb1:44d7'
    b = '2001:0200:0dff:fff1:0216:3eff:feb1:44d7'
    [b, b.upcase, "[#{b}]", "[#{b.upcase}]"].each { |host|
      dn = DomainName(host)
      assert_equal("[#{a}]", dn.uri_host)
      assert_equal(a, dn.hostname)
      assert_equal(true, dn.ipaddr?)
      assert_equal(IPAddr.new(a), dn.ipaddr)
      assert_equal(true, dn.cookie_domain?(host))
      assert_equal(true, dn.cookie_domain?(host, true))
      assert_equal(true, dn.cookie_domain?(dn))
      assert_equal(true, dn.cookie_domain?(dn, true))
      assert_equal(true, dn.cookie_domain?(a))
      assert_equal(true, dn.cookie_domain?(a, true))
      assert_equal(nil, dn.superdomain)
    }
  end

  should "get superdomain" do
    [
      %w[www.sub.example.local sub.example.local example.local local],
      %w[www.sub.example.com sub.example.com example.com com],
    ].each { |domain, *superdomains|
      dn = DomainName(domain)
      superdomains.each { |superdomain|
        sdn = DomainName(superdomain)
        assert_equal sdn, dn.superdomain
        dn = sdn
      }
      assert_equal nil, dn.superdomain
    }
  end

  should "have idn methods" do
    dn = DomainName("金八先生.B組.3年.日本語ドメイン名Example.日本")

    assert_equal "xn--44q1cv48kq8x.xn--b-gf6c.xn--3-pj3b.xn--example-6q4fyliikhk162btq3b2zd4y2o.xn--wgv71a", dn.hostname
    assert_equal "金八先生.b組.3年.日本語ドメイン名example.日本", dn.hostname_idn
    assert_equal "xn--example-6q4fyliikhk162btq3b2zd4y2o.xn--wgv71a", dn.domain
    assert_equal "日本語ドメイン名example.日本", dn.domain_idn
    assert_equal "xn--wgv71a", dn.tld
    assert_equal "日本", dn.tld_idn
  end
end
