#!/usr/bin/env ruby
#--
#   Copyright (C) 2001, 2002, 2003 Matt Armstrong.  All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
#
# 1. Redistributions of source code must retain the above copyright notice,
#    this list of conditions and the following disclaimer.
# 2. Redistributions in binary form must reproduce the above copyright
#    notice, this list of conditions and the following disclaimer in the
#    documentation and/or other materials provided with the distribution.
# 3. The name of the author may not be used to endorse or promote products
#    derived from this software without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
# IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
# OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN
# NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
# TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
# PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#

require 'tests/testbase'
require 'rmail/address'

class TestRMailAddress < TestBase

  def domain_optional
    # Set to true for tests that include addresses without a domain
    # portion.
    false
  end

  def method_list
    [:display_name, :name, :address, :comments,
      :format, :domain, :local]
  end

  def validate_method(object, method, *args)
    assert(method_list.include?(method),
	   "#{method.inspect} not in #{method_list.inspect}")
    assert_respond_to(method, object)
    ret = nil
    ret = object.send(method, *args)
    if block_given?
      yield(ret)
    end
  end

  def validate_interface(address)
    assert_instance_of(RMail::Address, address)

    validate_method(address, :display_name) { |ret|
      assert_instance_of(String, ret) unless ret.nil?
    }

    validate_method(address, :name) { |ret|
      assert_instance_of(String, ret) unless ret.nil?
    }

    validate_method(address, :address) { |ret|
      assert_instance_of(String, ret) unless ret.nil?
    }

    validate_method(address, :comments) { |ret|
      unless ret.nil?
	assert_instance_of(Array, ret)
	ret.each { |comment|
	  assert_instance_of(String, comment)
	}
      end
    }

    validate_method(address, :format) { |ret|
      assert_instance_of(String, ret) unless ret.nil?
    }

    validate_method(address, :domain) { |ret|
      assert_instance_of(String, ret) unless ret.nil?
    }

    validate_method(address, :local) { |ret|
      assert_instance_of(String, ret) unless ret.nil?
    }
  end

  def validate_case(testcase, debug = false)
    begin
      prev_debug = $DEBUG
      $DEBUG = debug
      results = RMail::Address.parse(testcase[0])
    ensure
      $DEBUG = prev_debug
    end
    assert_kind_of(Array, results)
    assert_kind_of(RMail::Address::List, results)
    results.each { |address|
      validate_interface(address)
    }
    expected_results = testcase[1]
    assert_instance_of(Array, expected_results)
    assert_equal(expected_results.length, results.length,
		 "results array wrong length, got #{results.inspect}")

    results.each_with_index { |address, i|
      assert_instance_of(Hash, expected_results[i])
      methods = method_list
      expected_results[i].each { |method, expected|
	validate_method(address, method) { |ret|
	  assert_equal(expected, ret,
		       "\nstring #{testcase[0].inspect}\naddr #{address.inspect}\nmethod #{method.inspect}\n")
	}
	methods.delete(method)
      }
      assert_equal(0, methods.length,
		   "test case did not test these methods #{methods.inspect}")
    }
  end

  def test_rfc_2822()

    # The following are from RFC2822
    validate_case\
    ['John Doe <jdoe@machine.example>',
      [ { :name => 'John Doe',
	  :display_name => 'John Doe',
	  :address => 'jdoe@machine.example',
	  :comments => nil,
	  :domain => 'machine.example',
	  :local => 'jdoe',
	  :format => 'John Doe <jdoe@machine.example>' } ]]

    validate_case\
    [' Mary Smith <mary@example.net>',
      [ { :name => 'Mary Smith',
	  :display_name => 'Mary Smith',
	  :address => 'mary@example.net',
	  :comments => nil,
	  :domain => 'example.net',
	  :local => 'mary',
	  :format => 'Mary Smith <mary@example.net>' } ]]

    validate_case\
    ['"Joe Q. Public" <john.q.public@example.com>',
      [ { :name => 'Joe Q. Public',
	  :display_name => 'Joe Q. Public',
	  :address => 'john.q.public@example.com',
	  :comments => nil,
	  :domain => 'example.com',
	  :local => 'john.q.public',
	  :format => '"Joe Q. Public" <john.q.public@example.com>' } ]]

    validate_case\
    ['Mary Smith <mary@x.test>, jdoe@example.org, Who? <one@y.test>',
      [ { :name => 'Mary Smith',
	  :display_name => 'Mary Smith',
	  :address => 'mary@x.test',
	  :comments => nil,
	  :domain => 'x.test',
	  :local => 'mary',
	  :format => 'Mary Smith <mary@x.test>' },
	{ :name => nil,
	  :display_name => nil,
	  :address => 'jdoe@example.org',
	  :comments => nil,
	  :domain => 'example.org',
	  :local => 'jdoe',
	  :format => 'jdoe@example.org' },
	{ :name => 'Who?',
	  :display_name => 'Who?',
	  :address => 'one@y.test',
	  :comments => nil,
	  :domain => 'y.test',
	  :local => 'one',
	  :format => 'Who? <one@y.test>' } ]]

    validate_case\
    ['<boss@nil.test>, "Giant; \"Big\" Box" <sysservices@example.net>',
      [ { :name => nil,
	  :display_name => nil,
	  :address => 'boss@nil.test',
	  :comments => nil,
	  :domain => 'nil.test',
	  :local => 'boss',
	  :format => 'boss@nil.test' },
	{ :name => 'Giant; "Big" Box',
	  :display_name => 'Giant; "Big" Box',
	  :address => 'sysservices@example.net',
	  :comments => nil,
	  :domain => 'example.net',
	  :local => 'sysservices',
	  :format => '"Giant; \"Big\" Box" <sysservices@example.net>' }
      ] ]

    validate_case\
    ['A Group:Chris Jones <c@a.test>,joe@where.test,John <jdoe@one.test>;',
      [ { :name => 'Chris Jones',
	  :display_name => 'Chris Jones',
	  :address => 'c@a.test',
	  :comments => nil,
	  :domain => 'a.test',
	  :local => 'c',
	  :format => 'Chris Jones <c@a.test>' },
	{ :name => nil,
	  :display_name => nil,
	  :address => 'joe@where.test',
	  :comments => nil,
	  :domain => 'where.test',
	  :local => 'joe',
	  :format => 'joe@where.test' },
	{ :name => 'John',
	  :display_name => 'John',
	  :address => 'jdoe@one.test',
	  :comments => nil,
	  :domain => 'one.test',
	  :local => 'jdoe',
	  :format => 'John <jdoe@one.test>' }
      ] ]

    validate_case\
    ['Undisclosed recipients:;',
      [] ]

    validate_case\
    ['undisclosed recipients: ;',
      [] ]

    validate_case\
    ['"Mary Smith: Personal Account" <smith@home.example>   ',
      [ { :name => 'Mary Smith: Personal Account',
	  :display_name => 'Mary Smith: Personal Account',
	  :address => 'smith@home.example',
	  :comments => nil,
	  :domain => 'home.example',
	  :local => 'smith',
	  :format => '"Mary Smith: Personal Account" <smith@home.example>' }
      ] ]

    validate_case\
    ['Pete(A wonderful \) chap) <pete(his account)@silly.test(his host)>',
      [ { :name => 'Pete',
	  :display_name => 'Pete',
	  :address => 'pete@silly.test',
	  :comments => ['A wonderful ) chap', 'his account', 'his host'],
	  :domain => 'silly.test',
	  :local => 'pete',
	  :format => 'Pete <pete@silly.test> (A wonderful \) chap) (his account) (his host)' }
      ] ]

    validate_case\
    ['A Group:a@b.c,d@e.f;',
      [ { :name => nil, :display_name => nil, :comments => nil,
	  :local => 'a', :domain => 'b.c', :format  => 'a@b.c',
	  :address => 'a@b.c' },
	{ :name => nil, :display_name => nil, :comments => nil,
	  :local => 'd', :domain => 'e.f', :format  => 'd@e.f',
	  :address => 'd@e.f' } ] ]

    validate_case\
    ["A Group(Some people)\r\n     :Chris Jones <c@(Chris's host.)public.example>,\r\n         joe@example.org",
      [ { :name => 'Chris Jones',
	  :display_name => 'Chris Jones',
	  :address => 'c@public.example',
	  :comments => ['Chris\'s host.'],
	  :domain => 'public.example',
	  :local => 'c',
	  :format => 'Chris Jones <c@public.example> (Chris\'s host.)' } ] ]

    validate_case\
    ["A Group(Some people)\r\n     :Chris Jones <c@(Chris's host.)public.example>,\r\n         joe@example.org;",
      [ { :name => 'Chris Jones',
	  :display_name => 'Chris Jones',
	  :address => 'c@public.example',
	  :comments => ['Chris\'s host.'],
	  :domain => 'public.example',
	  :local => 'c',
	  :format => 'Chris Jones <c@public.example> (Chris\'s host.)' },
	{ :name => nil,
	  :display_name => nil,
	  :address => 'joe@example.org',
	  :comments => nil,
	  :domain => 'example.org',
	  :local => 'joe',
	  :format => 'joe@example.org' }
	] ]

    validate_case\
    ['(Empty list)(start)Undisclosed recipients  :(nobody(that I know))  ;',
      [] ]

    # Note, the space is lost after the Q. because we always convert
    # word . word into "word.word" for output.  To preserve the space,
    # the guy should quote his name.
    # FIXME: maybe fix this behavior?
    validate_case\
    ['Joe Q. Public <john.q.public@example.com>',
      [ { :name => 'Joe Q.Public',
	  :display_name => 'Joe Q.Public',
	  :address => 'john.q.public@example.com',
	  :comments => nil,
	  :domain => 'example.com',
	  :local => 'john.q.public',
	  :format => '"Joe Q.Public" <john.q.public@example.com>' } ] ]

    # FIXME: maybe expect "B.J.Major" <bjbear71@example.net>
    validate_case\
    ['B.J. Major <bjbear71@example.net>',
      [ { :name => 'B.J.Major',
	  :display_name => 'B.J.Major',
	  :address => 'bjbear71@example.net',
	  :comments => nil,
	  :domain => 'example.net',
	  :local => 'bjbear71',
	  :format => '"B.J.Major" <bjbear71@example.net>' } ] ]

    validate_case\
    ['jdoe@test   . example',
      [ { :name => nil,
	  :display_name => nil,
	  :address => 'jdoe@test.example',
	  :comments => nil,
	  :domain => 'test.example',
	  :local => 'jdoe',
	  :format => 'jdoe@test.example' } ] ]

    validate_case\
    ['Mary Smith <@machine.tld:mary@example.net>, , jdoe@test   . example',
      [ { :name => 'Mary Smith',
	  :display_name => 'Mary Smith',
	  :address => 'mary@example.net',
	  :comments => nil,
	  :domain => 'example.net',
	  :local => 'mary',
	  :format => 'Mary Smith <mary@example.net>' },
	{ :name => nil,
	  :display_name => nil,
	  :address => 'jdoe@test.example',
	  :comments => nil,
	  :domain => 'test.example',
	  :local => 'jdoe',
	  :format => 'jdoe@test.example' } ] ]

    validate_case\
    ['John Doe <jdoe@machine(comment).  example>',
      [ { :name => 'John Doe',
	  :display_name => 'John Doe',
	  :address => 'jdoe@machine.example',
	  :comments => ['comment'],
	  :domain => 'machine.example',
	  :local => 'jdoe',
	  :format => 'John Doe <jdoe@machine.example> (comment)' } ] ]

    validate_case\
    ["Mary Smith\n\r                  \n          <mary@example.net>",
      [ { :name => 'Mary Smith',
	  :display_name => 'Mary Smith',
	  :address => 'mary@example.net',
	  :comments => nil,
	  :domain => 'example.net',
	  :local => 'mary',
	  :format => 'Mary Smith <mary@example.net>' } ] ]
  end

  # Tests stolen from majordomoII
  def test_majordomoII
    validate_case\
    ["tibbs@uh.edu (Homey (  j (\(\() t ) Tibbs)",
      [ { :name => 'Homey ( j ((() t ) Tibbs',
          :display_name => nil,
          :address => 'tibbs@uh.edu',
          :domain => 'uh.edu',
          :local => 'tibbs',
          :comments => [ 'Homey ( j ((() t ) Tibbs' ],
          :format => 'tibbs@uh.edu (Homey \( j \(\(\(\) t \) Tibbs)' } ] ]

    validate_case\
    ['"tibbs@home"@hpc.uh.edu (JLT )',
      [ { :name => 'JLT ',
          :address => 'tibbs@home@hpc.uh.edu',
          :display_name => nil,
          :domain => 'hpc.uh.edu',
          :local => 'tibbs@home',
          :comments => [ 'JLT ' ],
          :format => '"tibbs@home"@hpc.uh.edu (JLT )' } ] ]

    validate_case\
    ['tibbs@[129.7.3.5]',
      [ { :name => nil,
          :display_name => nil,
          :domain => '[129.7.3.5]',
          :local => 'tibbs',
          :comments => nil,
          :address => 'tibbs@[129.7.3.5]',
          :format => '<tibbs@[129.7.3.5]>' } ] ]

    validate_case\
    ['A_Foriegner%across.the.pond@relay1.uu.net',
      [ { :name => nil,
          :display_name => nil,
          :domain => 'relay1.uu.net',
          :local => 'A_Foriegner%across.the.pond',
          :comments => nil,
          :address => 'A_Foriegner%across.the.pond@relay1.uu.net',
          :format => 'A_Foriegner%across.the.pond@relay1.uu.net' } ] ]

    validate_case\
    ['"Jason @ Tibbitts" <tibbs@uh.edu>',
      [ { :name => 'Jason @ Tibbitts',
          :display_name => 'Jason @ Tibbitts',
          :domain => 'uh.edu',
          :local => 'tibbs',
          :comments => nil,
          :address => 'tibbs@uh.edu',
          :format => '"Jason @ Tibbitts" <tibbs@uh.edu>' } ] ]

    # Majordomo II parses all of these with an error.  We have deleted
    # some of the tests since when they are actually legal.
    validate_case ['tibbs@uh.edu Jason Tibbitts', [] ]
    validate_case ['@uh.edu', [] ] # Can't start with @
    validate_case ['J <tibbs>', [] ] # Not FQDN
    validate_case ['<tibbs Da Man', [] ] # Unbalanced
    validate_case ['Jason <tibbs>>', [] ] # Unbalanced
    validate_case ['tibbs, nobody', [] ] # Multiple addresses not allowed
    validate_case ['tibbs@.hpc', [] ] # Illegal @.
    validate_case ['<a@b>@c', [] ] # @ illegal in phrase
    validate_case ['<a@>', [] ]	# No hostname
    validate_case ['<a@b>.abc', [] ] # >. illegal
    validate_case ['<a@b> blah <d@e>', [] ] # Two routes illegal
    validate_case ['<tibbs<tib@a>@hpc.uh.edu>', [] ] # Nested routes illegal
    validate_case ['[<tibbs@hpc.uh.edu>]', [] ]	# Enclosed in []
    validate_case ['<a@b.cd> Me [blurfl] U', [] ] # Domain literals illegal in comment
    validate_case ['A B @ C <a@b.c>', [] ] # @ not legal in comment
    validate_case ['blah . tibbs@', [] ] # Unquoted . not legal in comment
    validate_case ['tibbs@hpc.uh.edu.', [] ] # Address ends with a dot
    validate_case ['sina.hpc.uh.edu', [] ] # No local-part@
    validate_case ['tibbs@@math.uh.edu', [] ] # Two @s next to each other
    validate_case ['tibbs@math@uh.edu', [] ] # Two @s, not next to each other
  end

  def test_mailtools_suite()

    #
    # The following are from the Perl MailTools module version 1.40
    #
    validate_case\
    ['"Joe & J. Harvey" <ddd @Org>, JJV @ BBN',
      [	{ :name => 'Joe & J. Harvey',
	  :display_name => 'Joe & J. Harvey',
	  :address => 'ddd@Org',
	  :comments => nil,
	  :domain => 'Org',
	  :local => 'ddd',
	  :format => '"Joe & J. Harvey" <ddd@Org>' },
	{ :name => nil,
	  :display_name => nil,
	  :address => 'JJV@BBN',
	  :comments => nil,
	  :domain => 'BBN',
	  :local => 'JJV',
	  :format => 'JJV@BBN' } ] ]

    validate_case\
    ['"spickett@tiac.net" <Sean.Pickett@zork.tiac.net>',
      [ { :name => 'spickett@tiac.net',
	  :display_name => 'spickett@tiac.net',
	  :address => 'Sean.Pickett@zork.tiac.net',
	  :comments => nil,
	  :domain => 'zork.tiac.net',
	  :local => 'Sean.Pickett',
	  :format => '"spickett@tiac.net" <Sean.Pickett@zork.tiac.net>' } ] ]

    validate_case\
    ['rls@intgp8.ih.att.com (-Schieve,R.L.)',
      [ { :name => '-Schieve,R.L.',
	  :display_name => nil,
	  :address => 'rls@intgp8.ih.att.com',
	  :comments => ['-Schieve,R.L.'],
	  :domain => 'intgp8.ih.att.com',
	  :local => 'rls',
	  :format => 'rls@intgp8.ih.att.com (-Schieve,R.L.)' } ] ]

    validate_case\
    ['jrh%cup.portal.com@portal.unix.portal.com',
      [ { :name => nil,
	  :display_name => nil,
	  :address => 'jrh%cup.portal.com@portal.unix.portal.com',
	  :comments => nil,
	  :domain => 'portal.unix.portal.com',
	  :local => 'jrh%cup.portal.com',
	  :format => 'jrh%cup.portal.com@portal.unix.portal.com' } ] ]

    validate_case\
    ['astrachan@austlcm.sps.mot.com (\'paul astrachan/xvt3\')',
      [ { :name => '\'paul astrachan/xvt3\'',
	  :display_name => nil,
	  :address => 'astrachan@austlcm.sps.mot.com',
	  :comments => ['\'paul astrachan/xvt3\''],
	  :domain => 'austlcm.sps.mot.com',
	  :local => 'astrachan',
	  :format =>
	  'astrachan@austlcm.sps.mot.com (\'paul astrachan/xvt3\')' } ] ]

    validate_case\
    ['TWINE57%SDELVB.decnet@SNYBUF.CS.SNYBUF.EDU (JAMES R. TWINE - THE NERD)',
      [ { :name => 'JAMES R. TWINE - THE NERD',
	  :display_name => nil,
	  :address => 'TWINE57%SDELVB.decnet@SNYBUF.CS.SNYBUF.EDU',
	  :comments => ['JAMES R. TWINE - THE NERD'],
	  :domain => 'SNYBUF.CS.SNYBUF.EDU',
	  :local => 'TWINE57%SDELVB.decnet',
	  :format =>
	  'TWINE57%SDELVB.decnet@SNYBUF.CS.SNYBUF.EDU (JAMES R. TWINE - THE NERD)'} ] ]

    validate_case\
    ['David Apfelbaum <da0g+@andrew.cmu.edu>',
      [ { :name => 'David Apfelbaum',
	  :display_name => 'David Apfelbaum',
	  :address => 'da0g+@andrew.cmu.edu',
	  :comments => nil,
	  :domain => 'andrew.cmu.edu',
	  :local => 'da0g+',
	  :format => 'David Apfelbaum <da0g+@andrew.cmu.edu>' } ] ]

    validate_case\
    ['"JAMES R. TWINE - THE NERD" <TWINE57%SDELVB%SNYDELVA.bitnet@CUNYVM.CUNY.EDU>',
      [ { :name => 'JAMES R. TWINE - THE NERD',
	  :display_name => 'JAMES R. TWINE - THE NERD',
	  :address => 'TWINE57%SDELVB%SNYDELVA.bitnet@CUNYVM.CUNY.EDU',
	  :comments => nil,
	  :domain => 'CUNYVM.CUNY.EDU',
	  :local => 'TWINE57%SDELVB%SNYDELVA.bitnet',
	  :format => '"JAMES R. TWINE - THE NERD" <TWINE57%SDELVB%SNYDELVA.bitnet@CUNYVM.CUNY.EDU>' } ] ]

    validate_case\
    ['/G=Owen/S=Smith/O=SJ-Research/ADMD=INTERSPAN/C=GB/@mhs-relay.ac.uk',
      [ { :name => nil,
	  :display_name => nil,
	  :address => '/G=Owen/S=Smith/O=SJ-Research/ADMD=INTERSPAN/C=GB/@mhs-relay.ac.uk',
	  :comments => nil,
	  :domain => 'mhs-relay.ac.uk',
	  :local => '/G=Owen/S=Smith/O=SJ-Research/ADMD=INTERSPAN/C=GB/',
	  :format => '/G=Owen/S=Smith/O=SJ-Research/ADMD=INTERSPAN/C=GB/@mhs-relay.ac.uk' } ] ]

    validate_case\
    ['"Stephen Burke, Liverpool" <BURKE@vxdsya.desy.de>',
      [ { :name => 'Stephen Burke, Liverpool',
	  :display_name => 'Stephen Burke, Liverpool',
	  :address => 'BURKE@vxdsya.desy.de',
	  :comments => nil,
	  :domain => 'vxdsya.desy.de',
	  :local => 'BURKE',
	  :format => '"Stephen Burke, Liverpool" <BURKE@vxdsya.desy.de>' } ] ]

    validate_case\
    ['The Newcastle Info-Server <info-admin@newcastle.ac.uk>',
      [ { :name => 'The Newcastle Info-Server',
	  :display_name => 'The Newcastle Info-Server',
	  :address => 'info-admin@newcastle.ac.uk',
	  :comments => nil,
	  :domain => 'newcastle.ac.uk',
	  :local => 'info-admin',
	  :format => 'The Newcastle Info-Server <info-admin@newcastle.ac.uk>'
	} ] ]

    validate_case\
    ['Suba.Peddada@eng.sun.com (Suba Peddada [CONTRACTOR])',
      [ { :name => 'Suba Peddada [CONTRACTOR]',
	  :display_name => nil,
	  :address => 'Suba.Peddada@eng.sun.com',
	  :comments => ['Suba Peddada [CONTRACTOR]'],
	  :domain => 'eng.sun.com',
	  :local => 'Suba.Peddada',
	  :format => 'Suba.Peddada@eng.sun.com (Suba Peddada [CONTRACTOR])'
	} ] ]

    validate_case\
    ['Paul Manser (0032 memo) <a906187@tiuk.ti.com>',
      [ { :name => 'Paul Manser',
	  :display_name => 'Paul Manser',
	  :address => 'a906187@tiuk.ti.com',
	  :comments => ['0032 memo'],
	  :domain => 'tiuk.ti.com',
	  :local => 'a906187',
	  :format => 'Paul Manser <a906187@tiuk.ti.com> (0032 memo)' } ] ]

    validate_case\
    ['"gregg (g.) woodcock" <woodcock@bnr.ca>',
      [ { :name => 'gregg (g.) woodcock',
	  :display_name => 'gregg (g.) woodcock',
	  :address => 'woodcock@bnr.ca',
	  :comments => nil,
	  :domain => 'bnr.ca',
	  :local => 'woodcock',
	  :format => '"gregg (g.) woodcock" <woodcock@bnr.ca>' } ] ]

    validate_case\
    ['Graham.Barr@tiuk.ti.com',
      [ { :name => nil,
	  :display_name => nil,
	  :address => 'Graham.Barr@tiuk.ti.com',
	  :comments => nil,
	  :domain => 'tiuk.ti.com',
	  :local => 'Graham.Barr',
	  :format => 'Graham.Barr@tiuk.ti.com' } ] ]

    if domain_optional
      validate_case\
      ['a909937 (Graham Barr          (0004 bodg))',
	[ { :name => 'Graham Barr (0004 bodg)',
	    :display_name => nil,
	    :address => 'a909937',
	    :comments => ['Graham Barr (0004 bodg)'],
	    :domain => nil,
	    :local => 'a909937',
	    :format => 'a909937 (Graham Barr \(0004 bodg\))' } ] ]
    end

    validate_case\
    ['david d `zoo\' zuhn <zoo@aggregate.com>',
      [ { :name => 'david d `zoo\' zuhn',
	  :display_name => 'david d `zoo\' zuhn',
	  :address => 'zoo@aggregate.com',
	  :comments => nil,
	  :domain => 'aggregate.com',
	  :local => 'zoo',
	  :format => 'david d `zoo\' zuhn <zoo@aggregate.com>' } ] ]

    validate_case\
    ['(foo@bar.com (foobar), ned@foo.com (nedfoo) ) <kevin@goess.org>',
      [ { :name => 'foo@bar.com (foobar), ned@foo.com (nedfoo) ',
	  :display_name => nil,
	  :address => 'kevin@goess.org',
	  :comments => ['foo@bar.com (foobar), ned@foo.com (nedfoo) '],
	  :domain => 'goess.org',
	  :local => 'kevin',
	  :format =>
	  'kevin@goess.org (foo@bar.com \(foobar\), ned@foo.com \(nedfoo\) )'
	} ]]
  end

  def test_rfc_822

    validate_case\
    ['":sysmail"@ Some-Group. Some-Org, Muhammed.(I am the greatest) Ali @(the)Vegas.WBA',
      [ { :name => nil,
	  :display_name => nil,
	  :address => ':sysmail@Some-Group.Some-Org',
	  :comments => nil,
	  :domain => 'Some-Group.Some-Org',
	  :local => ':sysmail',
	  :format => '":sysmail"@Some-Group.Some-Org' },
	{ :name => 'the',
	  :display_name => nil,
	  :address => 'Muhammed.Ali@Vegas.WBA',
	  :comments => ['I am the greatest', 'the'],
	  :domain => 'Vegas.WBA',
	  :local => 'Muhammed.Ali',
	  :format => 'Muhammed.Ali@Vegas.WBA (I am the greatest) (the)' } ] ]
  end

  def test_misc_addresses()

    # Make sure that parsing empty stuff works
    assert_equal([], RMail::Address.parse(nil))
    assert_equal([], RMail::Address.parse(""))
    assert_equal([], RMail::Address.parse(" "))
    assert_equal([], RMail::Address.parse("\t"))
    assert_equal([], RMail::Address.parse("\n"))
    assert_equal([], RMail::Address.parse("\r"))

    # From a bogus header I saw sent to ruby-talk
    validate_case([' ruby-talk@ruby-lang.org (ruby-talk ML),
	<ruby-talk ML <ruby-talk@ruby-lang.org>>',
                    [ { :name => 'ruby-talk ML',
                        :display_name => nil,
                        :comments => [ 'ruby-talk ML' ],
                        :domain => 'ruby-lang.org',
                        :local => 'ruby-talk',
			:address => 'ruby-talk@ruby-lang.org',
                        :format => 'ruby-talk@ruby-lang.org (ruby-talk ML)' }
                    ] ])

    # From Python address parsing bug list.  This is valid according
    # to RFC2822.
    validate_case(['Amazon.com <delivers-news2@amazon.com>',
		    [ { :name => 'Amazon.com',
			:display_name => 'Amazon.com',
			:address => 'delivers-news2@amazon.com',
			:comments => nil,
			:domain => 'amazon.com',
			:local => 'delivers-news2',
			:format => '"Amazon.com" <delivers-news2@amazon.com>'
		      } ] ])

    validate_case\
    ["\r\n  Amazon \r . \n com \t <    delivers-news2@amazon.com  >  \n  ",
      [ { :name => 'Amazon.com',
	  :display_name => 'Amazon.com',
	  :address => 'delivers-news2@amazon.com',
	  :comments => nil,
	  :domain => 'amazon.com',
	  :local => 'delivers-news2',
	  :format => '"Amazon.com" <delivers-news2@amazon.com>'
	} ] ]

    # From postfix-users@postfix.org
    # Date: Tue, 13 Nov 2001 10:58:23 -0800
    # Subject: Apparent bug in strict_rfc821_envelopes (Snapshot-20010714)
    validate_case\
    ['"mailto:rfc"@monkeys.test',
      [ { :name => nil,
	  :display_name => nil,
	  :address => 'mailto:rfc@monkeys.test',
	  :comments => nil,
	  :domain => 'monkeys.test',
	  :local => 'mailto:rfc',
	  :format => '"mailto:rfc"@monkeys.test' } ] ]

    # An unquoted mailto:rfc will end up being detected as an invalid
    # group display name.
    validate_case ['mailto:rfc@monkeys.test', [] ]

    # From gnu.emacs.help
    # Date: 24 Nov 2001 15:37:23 -0500
    validate_case\
    ['"Stefan Monnier <foo@acm.com>" <monnier+gnu.emacs.help/news/@flint.cs.yale.edu>',
      [ { :name => 'Stefan Monnier <foo@acm.com>',
	  :display_name => 'Stefan Monnier <foo@acm.com>',
	  :address => 'monnier+gnu.emacs.help/news/@flint.cs.yale.edu',
	  :comments => nil,
	  :domain => 'flint.cs.yale.edu',
	  :local => 'monnier+gnu.emacs.help/news/',
	  :format => '"Stefan Monnier <foo@acm.com>" <monnier+gnu.emacs.help/news/@flint.cs.yale.edu>' } ] ]

	{ :name => nil,
	  :display_name => nil,
	  :address => nil,
	  :comments => nil,
	  :domain => nil,
	  :local => nil,
	  :format => nil }

    validate_case\
    ['"foo:" . bar@somewhere.test',
      [ { :name => nil,
	  :display_name => nil,
	  :address => 'foo:.bar@somewhere.test',
	  :comments => nil,
	  :domain => 'somewhere.test',
	  :local => 'foo:.bar',
	  :format => '"foo:.bar"@somewhere.test' } ] ]

    validate_case\
    ['Some Dude <"foo:" . bar@somewhere.test>',
      [ { :name => 'Some Dude',
	  :display_name => 'Some Dude',
	  :address => 'foo:.bar@somewhere.test',
	  :comments => nil,
	  :domain => 'somewhere.test',
	  :local => 'foo:.bar',
	  :format => 'Some Dude <"foo:.bar"@somewhere.test>' } ] ]

    validate_case\
    ['"q\uo\ted"@example.com, Luke Skywalker <"use"."the.force"@space.test>',
      [ { :name => nil,
	  :display_name => nil,
	  :address => 'quoted@example.com',
	  :comments => nil,
	  :domain => 'example.com',
	  :local => 'quoted',
	  :format => 'quoted@example.com' },
	{ :name => 'Luke Skywalker',
	  :display_name => 'Luke Skywalker',
	  :address => 'use.the.force@space.test',
	  :comments => nil,
	  :domain => 'space.test',
	  :local => 'use.the.force',
	  :format => 'Luke Skywalker <use.the.force@space.test>' } ] ]

    validate_case\
    ['Erik =?ISO-8859-1?Q?B=E5gfors?= <erik@example.net>',
      [ { :name => 'Erik =?ISO-8859-1?Q?B=E5gfors?=',
	  :display_name => 'Erik =?ISO-8859-1?Q?B=E5gfors?=',
	  :address => 'erik@example.net',
	  :comments => nil,
	  :domain => 'example.net',
	  :local => 'erik',
	  :format => 'Erik =?ISO-8859-1?Q?B=E5gfors?= <erik@example.net>'
	} ] ]
  end

  def test_invalid_addresses()
    # The display name isn't encoded -- bad, but we parse it.
    validate_case\
    ["\322\315\322 \312\353\363\341 <bar@foo.invalid>",
      [ { :name => "\322\315\322 \312\353\363\341",
	  :display_name => "\322\315\322 \312\353\363\341",
	  :address => 'bar@foo.invalid',
	  :comments => nil,
	  :domain => 'foo.invalid',
	  :local => 'bar',
	  :format => "\"\322\315\322 \312\353\363\341\" <bar@foo.invalid>",
	} ] ]
  end

  def test_domain_literal

    validate_case\
    ['test@[domain]',
      [ { :name => nil,
	  :display_name => nil,
	  :address => 'test@[domain]',
	  :comments => nil,
	  :domain => '[domain]',
	  :local => 'test',
	  :format => '<test@[domain]>' } ] ]

    validate_case\
    ['<@[obsdomain]:test@[domain]>',
      [ { :name => nil,
	  :display_name => nil,
	  :address => 'test@[domain]',
	  :comments => nil,
	  :domain => '[domain]',
	  :local => 'test',
	  :format => '<test@[domain]>' } ] ]

    validate_case\
    ['<@[ob\]sd\\\\omain]:test@[dom\]ai\\\\n]>',
      [ { :name => nil,
	  :display_name => nil,
	  :address => 'test@[dom]ai\\n]',
	  :comments => nil,
	  :domain => '[dom]ai\\n]',
	  :local => 'test',
	  :format => '<test@[dom\]ai\\\\n]>' } ] ]

    validate_case\
    ["Bob \r<@machine.tld  \r,\n,,, @[obsdomain],@foo\t:\ntest @ [domain]>",
      [ { :name => 'Bob',
	  :display_name => 'Bob',
	  :address => 'test@[domain]',
	  :comments => nil,
	  :domain => '[domain]',
	  :local => 'test',
	  :format => 'Bob <test@[domain]>' } ] ]
  end

  def test_exhaustive()

    # We don't test every alphanumeric in atext -- assume that if a, m
    # and z work, they all will.
    atext = ('a'..'z').to_a + ('A'..'Z').to_a + ('0'..'9').to_a +
      '!#$%&\'*+-/=?^_`{|}~'.split(//) #/
    boring = ('b'..'l').to_a + ('n'..'o').to_a +
      ('p'..'y').to_a + ('B'..'L').to_a + ('N'..'O').to_a +
      ('P'..'Y').to_a + ('1'..'4').to_a + ('6'..'8').to_a

    (atext - boring).each {|ch|
      validate_case(["#{ch} <#{ch}@test>",
		      [ { :name => ch,
			  :display_name => ch,
			  :address => "#{ch}@test",
			  :comments => nil,
			  :domain => 'test',
			  :local => ch,
			  :format => ch + ' <' + ch + '@test>' } ] ])
    }

    validate_case([atext.join('') + ' <' + atext.join('') + '@test>',
		    [ { :name => atext.join(''),
			:display_name => atext.join(''),
			:address => atext.join('') + '@test',
			:comments => nil,
			:domain => 'test',
			:local => atext.join(''),
			:format => atext.join('') + ' <' + atext.join('') +
			'@test>' } ] ])

    ascii = (0..127).collect {|i| i.chr}
    whitespace = ["\r", "\n", ' ', "\t"]
    qtext = ascii - (whitespace + ['"', '\\'])
    ctext = ascii - (whitespace + ['(', ')', '\\'])
    dtext = ascii - (whitespace + ['[', ']', '\\'])

    (qtext - atext).each {|ch|
      validate_case(["\"#{ch}\" <\"#{ch}\"@test>",
		      [ { :name => ch,
			  :display_name => ch,
			  :address => "#{ch}@test",
			  :comments => nil,
			  :domain => 'test',
			  :local => "#{ch}",
			  :format => "\"#{ch}\" <\"#{ch}\"@test>" } ] ])
    }
    ['"', "\\"].each {|ch|
      validate_case(["\"\\#{ch}\" <\"\\#{ch}\"@test>",
		      [ { :name => ch,
			  :display_name => ch,
			  :address => ch + '@test',
			  :comments => nil,
			  :domain => 'test',
			  :local => ch,
			  :format => "\"\\#{ch}\" <\"\\#{ch}\"@test>" } ] ])
    }

    (ctext - boring).each {|ch|
      validate_case(["bob@test (#{ch})",
		      [ { :name => ch,
			  :display_name => nil,
			  :address => 'bob@test',
			  :comments => ["#{ch}"],
			  :domain => 'test',
			  :local => 'bob',
			  :format => "bob@test (#{ch})" } ] ])
      validate_case(["bob@test (\\#{ch})",
		      [ { :name => ch,
			  :display_name => nil,
			  :address => 'bob@test',
			  :comments => ["#{ch}"],
			  :domain => 'test',
			  :local => 'bob',
			  :format => "bob@test (#{ch})" } ] ])
    }
    [')', '(', '\\'].each {|ch|
      validate_case(["bob@test (\\#{ch})",
		      [ { :name => ch,
			  :display_name => nil,
			  :address => 'bob@test',
			  :comments => ["#{ch}"],
			  :domain => 'test',
			  :local => 'bob',
			  :format => 'bob@test (\\' + ch + ')' } ] ])
    }

    (dtext - boring).each {|ch|
      validate_case(["test@[\\#{ch}] (Sam)",
		      [ { :name => "Sam",
			  :display_name => nil,
			  :address => 'test@[' + ch + ']',
			  :comments => ["Sam"],
			  :domain => '[' + ch + ']',
			  :local => 'test',
			  :format => "<test@[#{ch}]> (Sam)" } ] ] )
      validate_case(["Sally <test@[\\#{ch}]>",
		      [ { :name => "Sally",
			  :display_name => "Sally",
			  :address => 'test@[' + ch + ']',
			  :comments => nil,
			  :domain => '[' + ch + ']',
			  :local => 'test',
			  :format => "Sally <test@[#{ch}]>" } ] ] )
    }

    validate_case(["test@[" + (dtext - boring).join('') + "]",
		    [ { :name => nil,
			:display_name => nil,
			:address => 'test@[' + (dtext - boring).join('') + "]",
			:comments => nil,
			:domain => '[' + (dtext - boring).join('') + ']',
			:local => 'test',
			:format => "<test@[" +
			(dtext - boring).join('') + "]>" } ] ])
    validate_case(["Bob <test@[" + (dtext - boring).join('') + "]>",
		    [ { :name => "Bob",
			:display_name => "Bob",
			:address => 'test@[' + (dtext - boring).join('') + "]",
			:comments => nil,
			:domain => '[' + (dtext - boring).join('') + ']',
			:local => 'test',
			:format => "Bob <test@[" +
			(dtext - boring).join('') + "]>" } ] ])
  end

  def test_out_of_spec()

    validate_case ['bodg fred@tiuk.ti.com', [] ]
    validate_case ['(comment) bodg fred@example.net, matt@example.com',
      [ { :name => nil, :display_name => nil, :comments => nil,
	  :address => 'matt@example.com',
	  :domain => 'example.com',
	  :local => 'matt',
	  :format => 'matt@example.com' } ] ]

    validate_case ['<Investor Alert@example.com>', [] ]

    validate_case ['"" <bob@example.com>',
      [ { :name => nil,
	  :display_name => nil,
	  :address => 'bob@example.com',
	  :comments => nil,
	  :domain => 'example.com',
	  :local => 'bob',
	  :format => 'bob@example.com' } ] ]

    validate_case\
    ['"" <""@example.com>',
      [ ] ]

    validate_case\
    ['@example.com',
      [ ] ]

    if domain_optional
      validate_case\
      ['bob',
	[ { :name => nil,
	    :display_name => nil,
	    :address => 'bob',
	    :comments => nil,
	    :domain => nil,
	    :local => 'bob',
	    :format => 'bob' } ] ]

      validate_case\
      ['bob,sally, sam',
	[ { :name => nil,
	    :display_name => nil,
	    :address => 'bob',
	    :comments => nil,
	    :domain => nil,
	    :local => 'bob',
	    :format => 'bob' },
	  { :name => nil,
	    :display_name => nil,
	    :address => 'sally',
	    :comments => nil,
	    :domain => nil,
	    :local => 'sally',
	    :format => 'sally' },
	  { :name => nil,
	    :display_name => nil,
	    :address => 'sam',
	    :comments => nil,
	    :domain => nil,
	    :local => 'sam',
	    :format => 'sam' }] ]
    else
      validate_case [ 'bob', [] ]
      validate_case [ 'Bobby, sally,sam', [] ]
    end

    validate_case(['Undisclosed <>', []])
    validate_case(['"Mensagem Automatica do Terra" <>', []])

    validate_case(["\177", []])
    validate_case(["\177\177\177", []])

  end

  def test_random_strings

    # These random strings have generated exceptions before, so test
    # them forever.
    RMail::Address.parse("j732[S\031\022\000\fuh\003Ye<2psd\005#1L=Hw*c\0247\006\aE\fXJ\026;\026\032zAAgpCFq+\010")
    RMail::Address.parse("\016o7=\024d^\001|h<,#\026~(<oS\005 f<u\022u+4\"\020d \023h\004)\036\016\023YY0\n]W]'\025S\t\035")
    RMail::Address.parse("<")
    RMail::Address.parse("J.:")
#     find_shortest_failure("\276\231J.^\351I:\2564") { |s|
#       RMail::Address.parse(s)
#     }

    0.upto(25) {
      specials = ',;\\()":@<>'
      strings = [(0..rand(255)).collect {rand(127).chr}.to_s,
	(0..255).collect {rand(255).chr}.to_s,
	(0..255).collect {
	  r = rand(specials.length * 5)
	  case r
	  when 0 .. specials.length - 1
	    specials[r].chr
	  else
	    rand(127).chr
	  end
	}.to_s ]
      strings.each {|string|
	assert_no_exception("failed for string #{string.inspect}") {
	  addrs = RMail::Address.parse(string)
	  addrs.each {|address|
	    method_list.each {|method|
	      address.send(method)
	    }
	  }
	}
      }
    }
  end

  def test_initialize
    addr = RMail::Address.new
    assert_instance_of(RMail::Address, addr)

    addr = RMail::Address.new("Bob Smith (Joe Bob) <bobs@example.com>")
    assert_instance_of(RMail::Address, addr)
    assert_equal("Bob Smith", addr.display_name)
    assert_equal("Bob Smith", addr.name)
    assert_equal("example.com", addr.domain)
    assert_equal("bobs", addr.local)

    addr = RMail::Address.new("Bob Smith (Joe Bob) <bobs@example.com>" +
			     ", Sally Joe (Hi Babe) <sallyj@test.example>")
    assert_instance_of(RMail::Address, addr)
    assert_equal("Bob Smith", addr.display_name)
    assert_equal("Bob Smith", addr.name)
    assert_equal("example.com", addr.domain)
    assert_equal("bobs", addr.local)

    addr = RMail::Address.new("\177\177")
    assert_instance_of(RMail::Address, addr)
    assert_equal(nil, addr.display_name)
    assert_equal(nil, addr.name)
    assert_equal(nil, addr.domain)
    assert_equal(nil, addr.local)

    e = assert_exception(ArgumentError) {
      RMail::Address.new(["bob"])
    }
    e = assert_exception(ArgumentError) {
      RMail::Address.new(Object.new)
    }
    e = assert_exception(ArgumentError) {
      RMail::Address.new(Hash.new)
    }
  end

  def test_comments
    a = RMail::Address.new
    assert_nil(a.comments)
    a.comments = [ 'foo', 'bar' ]
    assert_equal([ 'foo', 'bar' ], a.comments)
    a.comments = 'foo'
    assert_equal([ 'foo' ], a.comments)
  end

  def test_rmail_address_compare
    a1 = RMail::Address.new
    a2 = RMail::Address.new

    # Make sure it works on uninitialized addresses
    assert_equal(0, a1 <=> a2)
    assert_equal(a1, a2)

    # Display name and comments make no difference
    a1.display_name = 'foo'
    a1.comments = 'bar'
    assert_equal(0, a1 <=> a2)
    assert_equal(a1, a2)

    # a1 < a2
    a1 = RMail::Address.new("Zeus <bob@example.com>")
    a2 = RMail::Address.new("sally@example.com")
    assert(a1 < a2)
    assert(a1 != a2)

    # Type coercion
    assert(a1 < "zorton@example.com")
    assert(a1 > ".")
    assert(a1 == "bob@example.com")
  end

  def test_rmail_address_eql?
    a1 = RMail::Address.new
    a2 = RMail::Address.new

    # Make sure it works on uninitialized addresses
    assert(a1.eql?(a2))

    # Display name and comments make no difference
    a1.display_name = 'foo'
    a1.comments = 'bar'
    assert(a1.eql?(a2))

    a1 = RMail::Address.new("Zeus <bob@example.com>")
    assert_exception(TypeError) {
      a1.eql?("bob@eaxample.com")
    }
  end

  def test_rmail_address_hash
    assert_no_exception {
      RMail::Address.new.hash
    }
    a1 = RMail::Address.new("Zeus <bob@example.com>")
    assert_equal(a1.hash, "bob@example.com".hash)
  end

end
