specify_cli 0.0.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (106) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +4 -0
  3. data/.rspec +1 -0
  4. data/Gemfile +17 -0
  5. data/Gemfile.lock +117 -0
  6. data/LICENSE.txt +21 -0
  7. data/README.rdoc +43 -0
  8. data/Rakefile +15 -0
  9. data/bin/specify_cli +248 -0
  10. data/lib/specify.rb +45 -0
  11. data/lib/specify/branch_parser.rb +85 -0
  12. data/lib/specify/cli.rb +11 -0
  13. data/lib/specify/cli/database_setup.rb +46 -0
  14. data/lib/specify/cli/stubs.rb +63 -0
  15. data/lib/specify/cli/viewset.rb +21 -0
  16. data/lib/specify/configuration.rb +12 -0
  17. data/lib/specify/configuration/config.rb +120 -0
  18. data/lib/specify/configuration/db_config.rb +162 -0
  19. data/lib/specify/configuration/host_config.rb +37 -0
  20. data/lib/specify/database.rb +140 -0
  21. data/lib/specify/models.rb +43 -0
  22. data/lib/specify/models/accession.rb +33 -0
  23. data/lib/specify/models/agent.rb +138 -0
  24. data/lib/specify/models/app_resource_data.rb +32 -0
  25. data/lib/specify/models/app_resource_dir.rb +43 -0
  26. data/lib/specify/models/auto_numbering_scheme.rb +94 -0
  27. data/lib/specify/models/collecting_event.rb +38 -0
  28. data/lib/specify/models/collection.rb +67 -0
  29. data/lib/specify/models/collection_object.rb +127 -0
  30. data/lib/specify/models/createable.rb +21 -0
  31. data/lib/specify/models/determination.rb +63 -0
  32. data/lib/specify/models/discipline.rb +61 -0
  33. data/lib/specify/models/division.rb +26 -0
  34. data/lib/specify/models/geography.rb +5 -0
  35. data/lib/specify/models/geography/administrative_division.rb +32 -0
  36. data/lib/specify/models/geography/geographic_name.rb +66 -0
  37. data/lib/specify/models/geography/geography.rb +23 -0
  38. data/lib/specify/models/institution.rb +13 -0
  39. data/lib/specify/models/locality.rb +50 -0
  40. data/lib/specify/models/preparation.rb +53 -0
  41. data/lib/specify/models/preparation_type.rb +30 -0
  42. data/lib/specify/models/record_set.rb +55 -0
  43. data/lib/specify/models/record_set_item.rb +29 -0
  44. data/lib/specify/models/taxonomy.rb +6 -0
  45. data/lib/specify/models/taxonomy/common_name.rb +14 -0
  46. data/lib/specify/models/taxonomy/rank.rb +31 -0
  47. data/lib/specify/models/taxonomy/taxon.rb +54 -0
  48. data/lib/specify/models/taxonomy/taxonomy.rb +21 -0
  49. data/lib/specify/models/tree_queryable.rb +55 -0
  50. data/lib/specify/models/updateable.rb +20 -0
  51. data/lib/specify/models/user.rb +104 -0
  52. data/lib/specify/models/view_set_object.rb +32 -0
  53. data/lib/specify/number_format.rb +60 -0
  54. data/lib/specify/services.rb +18 -0
  55. data/lib/specify/services/service.rb +51 -0
  56. data/lib/specify/services/stub_generator.rb +291 -0
  57. data/lib/specify/services/view_loader.rb +177 -0
  58. data/lib/specify/session.rb +77 -0
  59. data/lib/specify/user_type.rb +61 -0
  60. data/lib/specify/version.rb +19 -0
  61. data/man/specify_cli-database.1 +60 -0
  62. data/man/specify_cli-database.1.html +137 -0
  63. data/man/specify_cli-database.1.ronn +53 -0
  64. data/man/specify_cli-repository.1 +55 -0
  65. data/man/specify_cli-repository.1.html +128 -0
  66. data/man/specify_cli-repository.1.ronn +42 -0
  67. data/man/specify_cli-stubs.1 +177 -0
  68. data/man/specify_cli-stubs.1.html +239 -0
  69. data/man/specify_cli-stubs.1.ronn +147 -0
  70. data/man/specify_cli-viewset.1 +92 -0
  71. data/man/specify_cli-viewset.1.html +154 -0
  72. data/man/specify_cli-viewset.1.ronn +72 -0
  73. data/man/specify_cli.1 +213 -0
  74. data/man/specify_cli.1.html +252 -0
  75. data/man/specify_cli.1.ronn +157 -0
  76. data/spec/branch_parser_spec.rb +94 -0
  77. data/spec/cli/stubs_spec.rb +44 -0
  78. data/spec/configuration/config_spec.rb +269 -0
  79. data/spec/configuration/db_config_spec.rb +299 -0
  80. data/spec/configuration/host_config_spec.rb +64 -0
  81. data/spec/database_spec.rb +83 -0
  82. data/spec/examples.txt +217 -0
  83. data/spec/helpers.rb +15 -0
  84. data/spec/models/app_resource_data_spec.rb +38 -0
  85. data/spec/models/app_resource_dir_spec.rb +8 -0
  86. data/spec/models/auto_numbering_scheme_spec.rb +78 -0
  87. data/spec/models/collection_object_spec.rb +92 -0
  88. data/spec/models/collection_spec.rb +32 -0
  89. data/spec/models/discipline_spec.rb +31 -0
  90. data/spec/models/record_set_spec.rb +18 -0
  91. data/spec/models/user_spec.rb +182 -0
  92. data/spec/models/view_set_object_spec.rb +70 -0
  93. data/spec/number_format_spec.rb +43 -0
  94. data/spec/services/stub_generator_spec.rb +635 -0
  95. data/spec/services/view_loader_spec.rb +436 -0
  96. data/spec/session_spec.rb +105 -0
  97. data/spec/spec_helper.rb +116 -0
  98. data/spec/support/db.yml +12 -0
  99. data/spec/support/stub.yaml +17 -0
  100. data/spec/support/stub_locality.yaml +19 -0
  101. data/spec/support/viewsets/paleo.views.xml +30 -0
  102. data/spec/support/viewsets/paleo.xml +30 -0
  103. data/spec/user_type_spec.rb +79 -0
  104. data/specify_cli.gemspec +27 -0
  105. data/specify_cli.rdoc +1 -0
  106. metadata +246 -0
@@ -0,0 +1,157 @@
1
+ specify_cli(1) -- A command line interface for Specify
2
+ ===============================================================================
3
+
4
+ ## SYNOPSIS
5
+
6
+ `specify_cli` [**-D**|**--database** <name>]
7
+ [**-H**|**--host** <name>]
8
+ [**-P**|**--password** <password>]
9
+ [**-U**|**--specify_user** <name>]
10
+ [**-c**|**--db_config** <file>]
11
+ [**-p**|**--port** <number>]
12
+ [**-u**|**--user** <name>]
13
+ <command> [<options>] [<args>]
14
+
15
+ ## DESCRIPTION
16
+ **specify_cli** is a tool that allows certain tasks in a Specify database to be
17
+ carried out from the command line.
18
+ Specify is a management system for biological collections developed by the
19
+ Specify Collections Consortium (http://www.sustain.specifysoftware.org).
20
+
21
+ **specify_cli** is an independent development that is not supported by the
22
+ Specify Collections Consortium. It operates directly on the MySQL/MariaDB
23
+ backend used by Specify. **Use at your own risk**.
24
+
25
+ Tasks currently supported:
26
+
27
+ * upload of user interface form definitions (*.views.xml* files) to the database
28
+ * generation of collection object stub records
29
+
30
+ ## FILES
31
+
32
+ `~/.specify_dbs.rc.yaml` is a YAML file that stores information about
33
+ Specify databases and directories containing *.views.xml* files.
34
+ The file has the general structure:
35
+
36
+ ---
37
+ :dir_names:
38
+ <view_file_directory>: <hostname>
39
+ ...
40
+ :hosts:
41
+ <hostname>:
42
+ :port: <port_number>
43
+ :databases:
44
+ <database_name>:
45
+ :db_user:
46
+ :name: <mysql_username>
47
+ :password: <password>
48
+ :sp_user: <specify_username>
49
+ ...
50
+ ...
51
+
52
+ The section `dir_names` contains directory-host-mappings. A directory-host-
53
+ mapping is a key-value-pair, where the key is the path of a directory on your
54
+ hard drive, the value is a host name. This is used to automatically resolve the
55
+ correct host for a *.views.xml* file based on the directory it is in. Add
56
+ mappings here by editing the file or using the **specify_cli-repository(1)**
57
+ command.
58
+
59
+ The section `hosts` contains settings for Specify databases by grouped by
60
+ `<hostname>`. For each host, the `port` and any `databases` can be configured in
61
+ this file by editing it or by using the **specify_cli-database(1)** command. The `databases` section contains connection settings for the individual databases by
62
+ `<database_name>`. The `db_user` is the MySQL/MariaDB user used to connect to
63
+ the database and will typically be the Specify *master user*. Leave `password`
64
+ blank to be prompted for the password when you run the command (this will not
65
+ work when triggered as a bash script from another application, e.g. a text
66
+ editor). Apart from the `db_user` `specify_cli` also needs a Specify user (a
67
+ user that Specify uses internally), which is provided under `sp_user`. Every
68
+ command (except the configuration commands **specify_cli-database(1)** and **specify_cli-repository(1)**) will start a session in Specify where the Specify
69
+ user will be logged in to the collection. Any records created or modified during
70
+ this session will be marked as created by or modified by the `sp_user`.
71
+
72
+ ## COMMANDS
73
+
74
+ * `specify_cli-database`(1)
75
+ Add a database configuration.
76
+ * `specify_cli-stubs`(1)
77
+ Create stub records
78
+ * `specify_cli-repository`(1)
79
+ Map a git repository to a host for automatic target resolution.
80
+ * `specify_cli-viewset`(1)
81
+ Upload a view to the database.
82
+
83
+ ## OPTIONS
84
+
85
+ * `-D`, `--database` <name>:
86
+ The name of the Specify database to connect to.
87
+ * `-H`, `--host` <name>:
88
+ The name or IP address of the host. Default: `localhost`
89
+ * `-P`, `--password` <password>:
90
+ The password for the MySQL/MariaDB user. Leave blank to be prompted.
91
+ * `-U`, `--specify_user` <name>:
92
+ The name of the Specify user.
93
+ * `-c`, `--db_config` <file>:
94
+ A database configuration file (if other than the default
95
+ `~/.specify_dbs.rc.yaml`)
96
+ * `-p`, `--port` <number>:
97
+ The port for the MySQL/MariaDB host.
98
+ * `-u`, `--user` <name>:
99
+ The MySQL/MariaDB user name.
100
+
101
+ ## EXAMPLES
102
+
103
+ Configure a database `Specify` on the host `specify.example.org`:
104
+
105
+ $ specify_cli --host=specify.example.org database specify
106
+
107
+ Map the current directory to the host `specify.example.org`:
108
+
109
+ $ specify_cli --host=specify.example.org repository -c
110
+
111
+ Map `~/specify_forms/invertpaleo` to `localhost`:
112
+
113
+ $ specify_cli repository ~/specify_forms/invertpaleo
114
+
115
+ Create 10 blank stub records in the collection `Triloites` in the database
116
+ `Specify` on localhost:
117
+
118
+ $ specify_cli -D Specify stubs Trilobites 10
119
+
120
+ Create 10 stub records for herbarium sheets in the collection `Orchids` in the
121
+ database `Specify` on localhost, determined to taxon `Orchidaceae`:
122
+
123
+ $ specify_cli -D Specify stubs --taxon='Family: Orchidaceae'\n
124
+ --preptype=Sheet --perpcount=1 Orchids 10
125
+
126
+ Upload `invertpaleo.views.xml` to discipline level for collection `Trilobites`
127
+ in the database `Specify` on localhost:
128
+
129
+ $ specify_cli -D Specify viewset -d Trilobites invertpaleo.views.xml
130
+
131
+ Upload `invertpaleo.views.xml` auto-resolving target from current Git branch:
132
+
133
+ $ specify_cli viewset -b invertpaleo.views.xml
134
+
135
+ ## LICENSE
136
+
137
+ MIT License
138
+
139
+ Copyright (c) 2018 Martin Stein
140
+
141
+ Permission is hereby granted, free of charge, to any person obtaining a copy
142
+ of this software and associated documentation files (the "Software"), to deal
143
+ in the Software without restriction, including without limitation the rights
144
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
145
+ copies of the Software, and to permit persons to whom the Software is
146
+ furnished to do so, subject to the following conditions:
147
+
148
+ The above copyright notice and this permission notice shall be included in all
149
+ copies or substantial portions of the Software.
150
+
151
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
152
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
153
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
154
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
155
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
156
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
157
+ SOFTWARE.
@@ -0,0 +1,94 @@
1
+ # frozen_string_literal: true
2
+ TEST_BRANCH = 'SPSPEC/TestCollection/user/specuser'
3
+
4
+ # Tests for the
5
+ module Specify
6
+ RSpec.describe BranchParser do
7
+
8
+
9
+ context 'when creating instances from branch names' do
10
+ let :config do
11
+ Pathname.new(Dir.pwd).join('spec', 'support', 'db.yml')
12
+ end
13
+
14
+ let(:path) { 'sp_resource' }
15
+
16
+ let(:collection_level) { 'SPSPEC/TestCollection/collection' }
17
+ let(:discipline_level) { 'SPSPEC/TestCollection/discipline' }
18
+ let(:user_type_level) { 'SPSPEC/TestCollection/Manager' }
19
+ let(:user_level) { 'SPSPEC/TestCollection/user/specuser' }
20
+
21
+ let :config do
22
+ Pathname.new(Dir.pwd).join('spec', 'support', 'db.yml')
23
+ end
24
+
25
+ context 'when collection' do
26
+ subject { described_class.new path, collection_level, config }
27
+
28
+ it do
29
+ is_expected.to have_attributes host: 'localhost',
30
+ database: 'SPSPEC',
31
+ collection: 'Test Collection',
32
+ level: :collection
33
+ end
34
+ end
35
+
36
+ context 'when discipline' do
37
+ subject { described_class.new path, discipline_level, config }
38
+
39
+ it do
40
+ is_expected.to have_attributes host: 'localhost',
41
+ database: 'SPSPEC',
42
+ collection: 'Test Collection',
43
+ level: :discipline
44
+ end
45
+ end
46
+
47
+ context 'when user type' do
48
+ subject { described_class.new path, user_type_level, config }
49
+
50
+ it do
51
+ is_expected.to have_attributes host: 'localhost',
52
+ database: 'SPSPEC',
53
+ collection: 'Test Collection',
54
+ level: { user_type: :manager }
55
+ end
56
+ end
57
+
58
+ context 'when user' do
59
+ subject { described_class.new path, user_level, config }
60
+
61
+ it do
62
+ is_expected.to have_attributes host: 'localhost',
63
+ database: 'SPSPEC',
64
+ collection: 'Test Collection',
65
+ level: { user: 'specuser' }
66
+ end
67
+ end
68
+ end
69
+
70
+ context 'when fetching the git branch', skip: true do
71
+ before :all do
72
+ @origin = `#{GIT_CURRENT_BRANCH}`.chomp
73
+ break if TEST_BRANCH == @origin
74
+ set_branch
75
+ end
76
+
77
+ it 'parses a name from the current branch' do
78
+ expect(described_class.current_branch)
79
+ .to have_attributes database: 'SPSPEC',
80
+ collection: 'Test Collection',
81
+ level: { user: 'specuser' }
82
+ end
83
+
84
+ it 'exits with an error message if the branch name is not parsable' do
85
+ expect { described_class.new('master') }
86
+ .to raise_error ArgumentError, BRANCH_ERROR + 'master'
87
+ end
88
+
89
+ after :all do
90
+ system("git checkout #{@origin}")
91
+ end
92
+ end
93
+ end
94
+ end
@@ -0,0 +1,44 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Specify
4
+ module CLI
5
+ RSpec.describe 'stubs' do
6
+ let :opts do
7
+ geo = 'Continent: North America; Country: United States;'\
8
+ ' locality: Springfield'
9
+ {
10
+ accession: "2018-AA-001",
11
+ cataloger: "specuser",
12
+ dataset: "Test dataset",
13
+ geography: geo,
14
+ locality: "Not transcribed",
15
+ taxon: "Kingdom: Plantae; Division: Tracheophyta",
16
+ preptype: "Sheet",
17
+ prepcount: "1",
18
+ file: nil
19
+ }
20
+ end
21
+
22
+ let :stub_params do
23
+ {
24
+ "dataset_name" => "Test dataset",
25
+ "cataloger" => "specuser",
26
+ "accession" => "2018-AA-001",
27
+ "collecting_data" => { "Continent" => "North America",
28
+ "Country" => "United States",
29
+ locality: 'Springfield' },
30
+ "default_locality_name" => "Not transcribed",
31
+ "determination" => { "Kingdom" => "Plantae",
32
+ "Division" => "Tracheophyta" },
33
+ "preparation" => { type: "Sheet", count: "1" }
34
+ }
35
+ end
36
+
37
+ describe '.stub_parameters' do
38
+ subject { CLI.stub_parameters opts }
39
+
40
+ it { is_expected.to eq stub_params }
41
+ end
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,269 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'fileutils'
4
+
5
+ # Tests for the
6
+ module Specify
7
+ module Configuration
8
+ RSpec.describe Config do
9
+ subject { described_class.new file }
10
+
11
+ let(:config) { described_class.empty empty_file }
12
+
13
+ let :file do
14
+ Pathname.new(Dir.pwd).join('spec', 'support', 'db.yml')
15
+ end
16
+
17
+ let :empty_file do
18
+ Pathname.new(Dir.pwd).join('spec', 'support', 'empty.yml')
19
+ end
20
+
21
+ let :file_dir_names do
22
+ a_hash_including('sp_resource' => 'localhost')
23
+ end
24
+
25
+ let :file_hosts do
26
+ db_user = a_hash_including name: 'specmaster',
27
+ password: 'masterpass'
28
+ spspec = a_hash_including db_user: db_user,
29
+ sp_user: 'specuser'
30
+ databases = a_hash_including 'SPSPEC' => spspec
31
+ localhost = a_hash_including port: 3306,
32
+ databases: databases
33
+ a_hash_including 'localhost' => localhost
34
+ end
35
+
36
+ let :hosts do
37
+ a_hash_including 'localhost' => a_hash_including(port: 3600)
38
+ end
39
+
40
+ it do
41
+ is_expected.to have_attributes dir_names: file_dir_names,
42
+ hosts: file_hosts
43
+ end
44
+
45
+ describe '.empty' do
46
+ subject { described_class.empty empty_file }
47
+
48
+ before { FileUtils.touch default_file }
49
+
50
+ let(:default_file) { File.expand_path('~/.specify_dbs.rc.yaml') }
51
+
52
+ let :params do
53
+ a_hash_including dir_names: an_instance_of(Hash).and(be_empty),
54
+ hosts: an_instance_of(Hash).and(be_empty)
55
+ end
56
+
57
+ it { is_expected.to have_attributes params: params }
58
+
59
+ context 'when passed no file but default file exists' do
60
+ subject(:make_empty) { described_class.empty file }
61
+
62
+ it do
63
+ error = "#{file} exists, won't overwrite"
64
+ expect { make_empty }.to raise_error error
65
+ end
66
+ end
67
+
68
+ context 'when passed the dafault file path and default file exists' do
69
+ subject(:make_empty) { described_class.empty default_file }
70
+
71
+ it do
72
+ error = "#{default_file} exists, won't overwrite"
73
+ expect { make_empty }.to raise_error error
74
+ end
75
+ end
76
+
77
+ context 'when given block mapping dir_names' do
78
+ subject do
79
+ described_class.empty(empty_file) do |config|
80
+ config.dir_names['specify_dir'] = 'localhost'
81
+ end
82
+ end
83
+
84
+ let :dir_names do
85
+ a_hash_including 'specify_dir' => 'localhost'
86
+ end
87
+
88
+ it { is_expected.to have_attributes dir_names: dir_names }
89
+ end
90
+
91
+ context 'when given block adding hosts' do
92
+ subject do
93
+ described_class.empty(empty_file) do |config|
94
+ config.add_host 'localhost', 3600
95
+ end
96
+ end
97
+
98
+ it do
99
+ is_expected.to have_attributes hosts: hosts
100
+ end
101
+ end
102
+
103
+ context 'when given block adding databases' do
104
+ subject do
105
+ described_class.empty(empty_file) do |config|
106
+ config.add_database 'SPSPEC', host: 'localhost' do |db|
107
+ db[:db_user][:name] = 'specmaster'
108
+ db[:db_user][:password] = 'masterpass'
109
+ db[:sp_user] = 'specuser'
110
+ end
111
+ end
112
+ end
113
+
114
+ let :hosts do
115
+ db_user = a_hash_including name: 'specmaster',
116
+ password: 'masterpass'
117
+ spspec = a_hash_including db_user: db_user,
118
+ sp_user: 'specuser'
119
+ databases = a_hash_including 'SPSPEC' => spspec
120
+ localhost = a_hash_including databases: databases
121
+ a_hash_including 'localhost' => localhost
122
+ end
123
+
124
+ it { is_expected.to have_attributes hosts: hosts }
125
+ end
126
+ end
127
+
128
+ describe '#add_host' do
129
+ subject(:add_host) { config.add_host 'localhost', 3600 }
130
+
131
+ context 'when adding a new host' do
132
+ it do
133
+ expect { add_host }
134
+ .to change(config, :hosts).from(be_empty).to hosts
135
+ end
136
+ end
137
+
138
+ context 'when adding an existing host' do
139
+ before { config.add_host 'localhost' }
140
+
141
+ it do
142
+ expect { add_host }
143
+ .to raise_error 'Host \'localhost\' already configured'
144
+ end
145
+ end
146
+ end
147
+
148
+ describe '#add_database' do
149
+ subject :add_database do
150
+ config.add_database 'SPSPEC', host: 'localhost' do |db|
151
+ db[:db_user][:name] = 'specmaster'
152
+ db[:db_user][:password] = 'masterpass'
153
+ db[:sp_user] = 'specuser'
154
+ end
155
+ end
156
+
157
+ let :databases do
158
+ db_user = a_hash_including name: 'specmaster',
159
+ password: 'masterpass'
160
+ spspec = a_hash_including db_user: db_user,
161
+ sp_user: 'specuser'
162
+ a_hash_including 'SPSPEC' => spspec
163
+ end
164
+
165
+ context 'when the database is not configured for the host' do
166
+ let :localhost_db do
167
+ localhost = a_hash_including port: nil,
168
+ databases: databases
169
+ a_hash_including 'localhost' => localhost
170
+ end
171
+
172
+ it do
173
+ expect { add_database }
174
+ .to change(config, :hosts)
175
+ .from(be_empty).to(localhost_db)
176
+ end
177
+ end
178
+
179
+ context 'when the database is already configured for the host' do
180
+ before { config.add_database 'SPSPEC', host: 'localhost' }
181
+
182
+ let(:e) { 'Database \'SPSPEC\' on \'localhost\' already configured' }
183
+
184
+ it do
185
+ expect { add_database }
186
+ .to raise_error e
187
+ end
188
+ end
189
+ end
190
+
191
+ describe '#params' do
192
+ subject { described_class.new(file).params }
193
+
194
+ it do
195
+ is_expected.to be_a(Hash)
196
+ .and include(dir_names: file_dir_names,
197
+ hosts: file_hosts)
198
+ end
199
+ end
200
+
201
+ describe '#save' do
202
+ subject(:save) { config.save }
203
+
204
+ let :dir_names do
205
+ a_hash_including 'specify_dir' => 'localhost'
206
+ end
207
+
208
+ let :hosts do
209
+ db_user = a_hash_including name: 'specmaster',
210
+ password: 'masterpass'
211
+ spspec = a_hash_including db_user: db_user,
212
+ sp_user: 'specuser'
213
+ databases = a_hash_including 'SPSPEC' => spspec
214
+ localhost = a_hash_including databases: databases,
215
+ port: 3600
216
+ a_hash_including 'localhost' => localhost
217
+ end
218
+
219
+ before do
220
+ config.dir_names['specify_dir'] = 'localhost'
221
+ config.add_host 'localhost', 3600
222
+ config.add_database 'SPSPEC', host: 'localhost' do |db|
223
+ db[:db_user][:name] = 'specmaster'
224
+ db[:db_user][:password] = 'masterpass'
225
+ db[:sp_user] = 'specuser'
226
+ end
227
+ end
228
+
229
+ it do
230
+ expect { save }
231
+ .to change { Psych.load_file(empty_file) }
232
+ .to include hosts: hosts, dir_names: dir_names
233
+ end
234
+
235
+ it do
236
+ expect { save }
237
+ .to change(config, :saved?).from(be_falsey).to be_truthy
238
+ end
239
+ end
240
+
241
+ describe '#saved?' do
242
+ subject { config.saved? }
243
+
244
+ context 'when the instance has not been modified' do
245
+ it { is_expected.to be_truthy }
246
+ end
247
+
248
+ context 'when the instance has been modified' do
249
+ before { config.touch }
250
+
251
+ it { is_expected.to be_falsey }
252
+ end
253
+ end
254
+
255
+ describe '#touch' do
256
+ subject(:touch) { config.touch }
257
+
258
+ it do
259
+ expect { touch }
260
+ .to change(config, :saved?).from(be_truthy).to be_falsey
261
+ end
262
+ end
263
+
264
+ after do
265
+ File.delete(empty_file) if File.exist?(empty_file)
266
+ end
267
+ end
268
+ end
269
+ end