1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
require_relative "../test_helper"

require "digest"
require "pathname"
require "tmpdir"

require "pro_tacts/contact"

class ContactTest < Minitest::Test
  def with_contacts(files)
    Dir.mktmpdir do |dir|
      directory = Pathname.new(dir)
      files.each { |name, content| File.write(directory / name, content) }
      yield directory
    end
  end

  def test_all_parses_every_contact
    with_contacts({
      "znorth.kdl" => "name \"Zed\"",
      "aiden.kdl" => "name \"Aiden\"",
    }) do |directory|
      ids = ProTacts::Contact.all(directory).map { it.id }

      assert_includes ids, "aiden"
      assert_includes ids, "znorth"
    end
  end

  def test_the_uid_comes_from_the_filename
    with_contacts({"kqmtnwpxlrvszoyp.kdl" => "name \"Aiden\""}) do |directory|
      assert_includes ProTacts::Contact.all(directory).first.vcard, "UID:kqmtnwpxlrvszoyp"
    end
  end

  def test_the_etag_hashes_the_rendered_card
    with_contacts({"aiden.kdl" => "name \"Aiden\""}) do |directory|
      contact = ProTacts::Contact.all(directory).first

      assert_equal %("#{Digest::SHA256.hexdigest(contact.vcard)}"), contact.etag
    end
  end

  def test_the_etag_is_stable_across_parses_and_moves_with_content
    with_contacts({"aiden.kdl" => "name \"Aiden\""}) do |directory|
      etag = ProTacts::Contact.all(directory).first.etag
      assert_equal etag, ProTacts::Contact.all(directory).first.etag

      File.write(directory / "aiden.kdl", "name \"Aiden Smith\"")
      refute_equal etag, ProTacts::Contact.all(directory).first.etag
    end
  end

  def test_an_empty_directory_lists_no_contacts
    with_contacts({}) do |directory|
      assert_empty ProTacts::Contact.all(directory)
    end
  end

  def test_dotfiles_are_ignored
    with_contacts({
      ".DS_Store" => "junk",
      "aiden.kdl" => "name \"Aiden\"",
    }) do |directory|
      assert_equal %w[aiden], ProTacts::Contact.all(directory).map { it.id }
    end
  end

  def test_a_missing_directory_raises
    error = assert_raises(Errno::ENOENT) do
      ProTacts::Contact.all(Pathname.new(Dir.mktmpdir) / "nonexistent")
    end

    assert_match(/nonexistent/, error.message)
  end

  def test_non_kdl_files_raise
    with_contacts({"notes.txt" => "hello"}) do |directory|
      error = assert_raises(ArgumentError) { ProTacts::Contact.all(directory) }

      assert_equal "invalid contact id: notes.txt", error.message
    end
  end

  def test_filenames_that_are_not_ids_raise
    with_contacts({"John Smith.kdl" => "name \"John\""}) do |directory|
      error = assert_raises(ArgumentError) { ProTacts::Contact.all(directory) }

      assert_equal "invalid contact id: John Smith", error.message
    end
  end

  def test_unparseable_files_raise
    with_contacts({"broken.kdl" => "contact {"}) do |directory|
      assert_raises(KDL::ParseError) { ProTacts::Contact.all(directory) }
    end
  end

  def test_files_whose_keys_are_not_contact_fields_raise
    with_contacts({"person.kdl" => "person { name \"A\" }"}) do |directory|
      error = assert_raises(ArgumentError) { ProTacts::Contact.all(directory) }

      assert_equal "unknown key in contact: person", error.message
    end
  end

  def test_empty_files_raise
    with_contacts({"empty.kdl" => ""}) do |directory|
      error = assert_raises(ArgumentError) { ProTacts::Contact.all(directory) }

      assert_equal "contact requires a name", error.message
    end
  end

  def test_files_whose_card_cannot_render_raise
    with_contacts({"nameless.kdl" => "phone \"+1-555-1234\""}) do |directory|
      error = assert_raises(ArgumentError) { ProTacts::Contact.all(directory) }

      assert_equal "contact requires a name", error.message
    end
  end
end