moose-inventory 1.0.9 → 2.1
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.
- checksums.yaml +4 -4
- data/.github/workflows/ci.yml +15 -1
- data/.github/workflows/release.yml +60 -0
- data/.gitignore +2 -1
- data/.gitleaks.toml +9 -0
- data/.rubocop.yml +49 -0
- data/BACKLOG.md +752 -24
- data/Gemfile +2 -0
- data/Gemfile.lock +36 -1
- data/README.md +340 -44
- data/Rakefile +2 -0
- data/bin/moose-inventory +2 -1
- data/docs/architecture/architecture-and-trust-boundaries.md +444 -0
- data/docs/compatibility/cli-output-compatibility.md +76 -0
- data/docs/governance/approval-register.md +37 -0
- data/docs/maintenance/database-backup-restore-guidance.md +162 -0
- data/docs/maintenance/package-maintenance-and-agent-boundaries.md +260 -0
- data/docs/process/conformance-gap-analysis-2026-05-28.md +192 -0
- data/docs/product/product-brief.md +161 -0
- data/docs/product/requirements-baseline.md +477 -0
- data/docs/qa/qa-documentation-and-release-gates.md +283 -0
- data/docs/release/package-provenance-hardening.md +126 -0
- data/docs/release/publishing.md +54 -50
- data/docs/release/release-environment-protection.md +70 -0
- data/docs/release/release-readiness.md +37 -4
- data/docs/security/accepted-risk-register.md +84 -0
- data/docs/security/security-privacy-process.md +287 -0
- data/docs/security-audit-2026-05-26-rerun.md +75 -0
- data/docs/security-audit-2026-05-26.md +63 -0
- data/docs/ux/cli-workflow-notes.md +287 -0
- data/examples/ansible/ansible.cfg +3 -0
- data/examples/ansible/inventory/moose_inventory.yml +5 -0
- data/examples/ansible/inventory_plugins/moose_inventory.py +100 -0
- data/examples/ci/README.md +16 -0
- data/examples/ci/github-actions/inventory-review.yml +38 -0
- data/examples/ci/inventory/example-snapshot.yml +19 -0
- data/examples/ci/scripts/validate-inventory-snapshot.sh +30 -0
- data/lib/moose_inventory/cli/application.rb +133 -5
- data/lib/moose_inventory/cli/association_rendering.rb +74 -0
- data/lib/moose_inventory/cli/association_rendering_support.rb +89 -0
- data/lib/moose_inventory/cli/audit.rb +62 -0
- data/lib/moose_inventory/cli/audit_recording.rb +40 -0
- data/lib/moose_inventory/cli/child_relation_rendering.rb +110 -0
- data/lib/moose_inventory/cli/console.rb +135 -0
- data/lib/moose_inventory/cli/db.rb +64 -0
- data/lib/moose_inventory/cli/factory.rb +28 -0
- data/lib/moose_inventory/cli/formatter.rb +8 -12
- data/lib/moose_inventory/cli/group.rb +7 -1
- data/lib/moose_inventory/cli/group_add.rb +91 -73
- data/lib/moose_inventory/cli/group_addchild.rb +41 -66
- data/lib/moose_inventory/cli/group_addhost.rb +33 -71
- data/lib/moose_inventory/cli/group_addvar.rb +27 -47
- data/lib/moose_inventory/cli/group_get.rb +8 -42
- data/lib/moose_inventory/cli/group_list.rb +7 -40
- data/lib/moose_inventory/cli/group_listvars.rb +9 -55
- data/lib/moose_inventory/cli/group_rm.rb +105 -73
- data/lib/moose_inventory/cli/group_rmchild.rb +47 -57
- data/lib/moose_inventory/cli/group_rmhost.rb +34 -61
- data/lib/moose_inventory/cli/group_rmvar.rb +30 -41
- data/lib/moose_inventory/cli/group_tags.rb +33 -0
- data/lib/moose_inventory/cli/helpers.rb +143 -0
- data/lib/moose_inventory/cli/host.rb +8 -2
- data/lib/moose_inventory/cli/host_add.rb +91 -66
- data/lib/moose_inventory/cli/host_addgroup.rb +39 -66
- data/lib/moose_inventory/cli/host_addvar.rb +28 -52
- data/lib/moose_inventory/cli/host_get.rb +9 -37
- data/lib/moose_inventory/cli/host_list.rb +24 -21
- data/lib/moose_inventory/cli/host_listvars.rb +9 -62
- data/lib/moose_inventory/cli/host_rm.rb +60 -42
- data/lib/moose_inventory/cli/host_rmgroup.rb +39 -55
- data/lib/moose_inventory/cli/host_rmvar.rb +31 -45
- data/lib/moose_inventory/cli/host_tags.rb +33 -0
- data/lib/moose_inventory/cli/listvars_support.rb +55 -0
- data/lib/moose_inventory/cli/plan_rendering.rb +50 -0
- data/lib/moose_inventory/cli/relation_transaction_support.rb +51 -0
- data/lib/moose_inventory/cli/tag_support.rb +97 -0
- data/lib/moose_inventory/cli/variable_rendering.rb +67 -0
- data/lib/moose_inventory/config/config.rb +185 -108
- data/lib/moose_inventory/db/db.rb +188 -193
- data/lib/moose_inventory/db/exceptions.rb +6 -3
- data/lib/moose_inventory/db/models.rb +16 -0
- data/lib/moose_inventory/db/schema_migrations.rb +248 -0
- data/lib/moose_inventory/inventory_context.rb +116 -0
- data/lib/moose_inventory/operations/add_associations.rb +131 -0
- data/lib/moose_inventory/operations/add_groups.rb +123 -0
- data/lib/moose_inventory/operations/add_hosts.rb +123 -0
- data/lib/moose_inventory/operations/add_variables.rb +77 -0
- data/lib/moose_inventory/operations/entity_variable_operation_support.rb +46 -0
- data/lib/moose_inventory/operations/group_child_relations.rb +125 -0
- data/lib/moose_inventory/operations/group_cleanup.rb +70 -0
- data/lib/moose_inventory/operations/import_inventory_snapshot.rb +41 -0
- data/lib/moose_inventory/operations/inventory_doctor.rb +172 -0
- data/lib/moose_inventory/operations/inventory_snapshot.rb +60 -0
- data/lib/moose_inventory/operations/inventory_snapshot_applier.rb +112 -0
- data/lib/moose_inventory/operations/inventory_snapshot_preview.rb +174 -0
- data/lib/moose_inventory/operations/inventory_snapshot_validator.rb +134 -0
- data/lib/moose_inventory/operations/operation_event_support.rb +27 -0
- data/lib/moose_inventory/operations/query_inventory/base_query.rb +24 -0
- data/lib/moose_inventory/operations/query_inventory/group_queries.rb +86 -0
- data/lib/moose_inventory/operations/query_inventory/host_queries.rb +106 -0
- data/lib/moose_inventory/operations/query_inventory.rb +47 -0
- data/lib/moose_inventory/operations/remove_associations.rb +113 -0
- data/lib/moose_inventory/operations/remove_groups.rb +79 -0
- data/lib/moose_inventory/operations/remove_hosts.rb +68 -0
- data/lib/moose_inventory/operations/remove_variables.rb +67 -0
- data/lib/moose_inventory/runtime_options.rb +31 -0
- data/lib/moose_inventory/version.rb +3 -1
- data/lib/moose_inventory.rb +10 -7
- data/moose-inventory.gemspec +22 -35
- data/scripts/check.sh +3 -0
- data/scripts/ci/check_generated_artifacts.sh +41 -0
- data/scripts/ci/check_permissions.sh +5 -0
- data/scripts/ci/check_rubocop.sh +33 -0
- data/scripts/ci/check_secrets.sh +26 -0
- data/scripts/ci/check_security.sh +18 -0
- data/scripts/ci/install_security_tools.sh +47 -0
- data/scripts/files.rb +5 -4
- data/scripts/install_dependencies.sh +2 -0
- data/spec/examples/ci_examples_spec.rb +37 -0
- data/spec/lib/moose_inventory/ansible_plugin_examples_spec.rb +29 -0
- data/spec/lib/moose_inventory/cli/application_doctor_spec.rb +50 -0
- data/spec/lib/moose_inventory/cli/application_import_export_spec.rb +100 -0
- data/spec/lib/moose_inventory/cli/application_spec.rb +25 -15
- data/spec/lib/moose_inventory/cli/audit_spec.rb +56 -0
- data/spec/lib/moose_inventory/cli/cli_spec.rb +15 -19
- data/spec/lib/moose_inventory/cli/console_spec.rb +98 -0
- data/spec/lib/moose_inventory/cli/factory_spec.rb +27 -0
- data/spec/lib/moose_inventory/cli/formatter_spec.rb +95 -3
- data/spec/lib/moose_inventory/cli/group_add_spec.rb +140 -116
- data/spec/lib/moose_inventory/cli/group_addchild_spec.rb +89 -35
- data/spec/lib/moose_inventory/cli/group_addhost_spec.rb +81 -84
- data/spec/lib/moose_inventory/cli/group_addvar_spec.rb +65 -68
- data/spec/lib/moose_inventory/cli/group_get_spec.rb +17 -33
- data/spec/lib/moose_inventory/cli/group_list_spec.rb +16 -38
- data/spec/lib/moose_inventory/cli/group_listvar_spec.rb +33 -40
- data/spec/lib/moose_inventory/cli/group_rm_spec.rb +165 -85
- data/spec/lib/moose_inventory/cli/group_rmchild_spec.rb +100 -30
- data/spec/lib/moose_inventory/cli/group_rmhost_spec.rb +76 -78
- data/spec/lib/moose_inventory/cli/group_rmvar_spec.rb +57 -63
- data/spec/lib/moose_inventory/cli/group_spec.rb +2 -0
- data/spec/lib/moose_inventory/cli/helpers_spec.rb +146 -0
- data/spec/lib/moose_inventory/cli/host_add_spec.rb +170 -116
- data/spec/lib/moose_inventory/cli/host_addgroup_spec.rb +100 -83
- data/spec/lib/moose_inventory/cli/host_addvar_spec.rb +92 -74
- data/spec/lib/moose_inventory/cli/host_get_spec.rb +14 -33
- data/spec/lib/moose_inventory/cli/host_list_spec.rb +41 -33
- data/spec/lib/moose_inventory/cli/host_listvar_spec.rb +45 -53
- data/spec/lib/moose_inventory/cli/host_rm_spec.rb +66 -48
- data/spec/lib/moose_inventory/cli/host_rmgroup_spec.rb +73 -83
- data/spec/lib/moose_inventory/cli/host_rmvar_spec.rb +56 -63
- data/spec/lib/moose_inventory/cli/host_spec.rb +2 -0
- data/spec/lib/moose_inventory/cli/tags_spec.rb +81 -0
- data/spec/lib/moose_inventory/config/config_spec.rb +41 -3
- data/spec/lib/moose_inventory/db/db_spec.rb +551 -29
- data/spec/lib/moose_inventory/db/exceptions_spec.rb +18 -0
- data/spec/lib/moose_inventory/db/models_spec.rb +7 -3
- data/spec/lib/moose_inventory/db_lifecycle_spec.rb +73 -0
- data/spec/lib/moose_inventory/inventory_context_spec.rb +10 -0
- data/spec/lib/moose_inventory/operations/add_associations_spec.rb +111 -0
- data/spec/lib/moose_inventory/operations/add_groups_spec.rb +80 -0
- data/spec/lib/moose_inventory/operations/add_hosts_spec.rb +82 -0
- data/spec/lib/moose_inventory/operations/add_variables_spec.rb +103 -0
- data/spec/lib/moose_inventory/operations/group_child_relations_spec.rb +122 -0
- data/spec/lib/moose_inventory/operations/import_inventory_snapshot_spec.rb +226 -0
- data/spec/lib/moose_inventory/operations/inventory_doctor_spec.rb +77 -0
- data/spec/lib/moose_inventory/operations/inventory_snapshot_spec.rb +50 -0
- data/spec/lib/moose_inventory/operations/operation_event_support_spec.rb +78 -0
- data/spec/lib/moose_inventory/operations/query_inventory_spec.rb +146 -0
- data/spec/lib/moose_inventory/operations/remove_associations_spec.rb +113 -0
- data/spec/lib/moose_inventory/operations/remove_groups_spec.rb +78 -0
- data/spec/lib/moose_inventory/operations/remove_hosts_spec.rb +55 -0
- data/spec/lib/moose_inventory/operations/remove_variables_spec.rb +83 -0
- data/spec/shared/shared_config_setup.rb +4 -3
- data/spec/spec_helper.rb +50 -40
- data/spec/support/cli_harness.rb +33 -0
- metadata +163 -35
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
require 'spec_helper'
|
|
2
4
|
require 'fileutils'
|
|
3
5
|
require 'tmpdir'
|
|
@@ -5,17 +7,23 @@ require 'tmpdir'
|
|
|
5
7
|
RSpec.describe 'Moose::Inventory::DB' do
|
|
6
8
|
def with_db_config(db_config)
|
|
7
9
|
saved_db = @db.instance_variable_get(:@db)
|
|
10
|
+
saved_models = @db.instance_variable_get(:@models)
|
|
11
|
+
saved_exceptions = @db.instance_variable_get(:@exceptions)
|
|
8
12
|
saved_settings = @config._settings.dup
|
|
9
13
|
|
|
10
14
|
begin
|
|
11
|
-
@db.
|
|
15
|
+
@db.reset_runtime_state
|
|
16
|
+
@db.init_exceptions
|
|
12
17
|
@config._settings.clear
|
|
13
18
|
@config._settings[:config] = { db: db_config }
|
|
14
19
|
yield
|
|
15
20
|
ensure
|
|
16
21
|
current_db = @db.instance_variable_get(:@db)
|
|
17
22
|
current_db.disconnect if current_db.respond_to?(:disconnect)
|
|
23
|
+
@db.reset_runtime_state
|
|
18
24
|
@db.instance_variable_set(:@db, saved_db)
|
|
25
|
+
@db.instance_variable_set(:@models, saved_models)
|
|
26
|
+
@db.instance_variable_set(:@exceptions, saved_exceptions)
|
|
19
27
|
@config._settings.clear
|
|
20
28
|
@config._settings.merge!(saved_settings)
|
|
21
29
|
end
|
|
@@ -28,9 +36,9 @@ RSpec.describe 'Moose::Inventory::DB' do
|
|
|
28
36
|
before(:all) do
|
|
29
37
|
# Set up the configuration object
|
|
30
38
|
@mockarg_parts = {
|
|
31
|
-
config:
|
|
32
|
-
format:
|
|
33
|
-
env:
|
|
39
|
+
config: File.join(spec_root, 'config/config.yml'),
|
|
40
|
+
format: 'yaml',
|
|
41
|
+
env: 'test'
|
|
34
42
|
}
|
|
35
43
|
|
|
36
44
|
@mockargs = []
|
|
@@ -69,39 +77,235 @@ RSpec.describe 'Moose::Inventory::DB' do
|
|
|
69
77
|
end
|
|
70
78
|
|
|
71
79
|
it 'raises a Moose DB exception for unsupported adapters' do
|
|
72
|
-
|
|
73
|
-
saved_models = @db.instance_variable_get(:@models)
|
|
74
|
-
saved_exceptions = @db.instance_variable_get(:@exceptions)
|
|
75
|
-
saved_settings = @config._settings.dup
|
|
76
|
-
|
|
77
|
-
begin
|
|
78
|
-
@db.instance_variable_set(:@db, nil)
|
|
79
|
-
@db.instance_variable_set(:@models, nil)
|
|
80
|
-
@db.instance_variable_set(:@exceptions, nil)
|
|
81
|
-
@config._settings.clear
|
|
82
|
-
@config._settings[:config] = { db: { adapter: 'unsupported' } }
|
|
83
|
-
|
|
80
|
+
with_db_config(adapter: 'unsupported') do
|
|
84
81
|
expect { @db.init }.to raise_error(
|
|
85
82
|
Moose::Inventory::DB::MooseDBException,
|
|
86
83
|
/database adapter unsupported is not yet supported/
|
|
87
84
|
)
|
|
88
85
|
expect(@db.exceptions[:moose]).to eq(Moose::Inventory::DB::MooseDBException)
|
|
89
|
-
ensure
|
|
90
|
-
@db.instance_variable_set(:@db, saved_db)
|
|
91
|
-
@db.instance_variable_set(:@models, saved_models)
|
|
92
|
-
@db.instance_variable_set(:@exceptions, saved_exceptions)
|
|
93
|
-
@config._settings.clear
|
|
94
|
-
@config._settings.merge!(saved_settings)
|
|
95
86
|
end
|
|
96
87
|
end
|
|
97
88
|
end
|
|
98
89
|
|
|
90
|
+
describe 'existing database upgrade behavior' do
|
|
91
|
+
def with_sqlite_fixture(schema_version: nil, tables: [])
|
|
92
|
+
Dir.mktmpdir('moose-schema-fixture') do |dir|
|
|
93
|
+
dbfile = File.join(dir, 'inventory.sqlite3')
|
|
94
|
+
seed_sqlite_fixture(dbfile, schema_version: schema_version, tables: tables)
|
|
95
|
+
|
|
96
|
+
with_db_config(adapter: 'sqlite3', file: dbfile) do
|
|
97
|
+
yield dbfile
|
|
98
|
+
end
|
|
99
|
+
end
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
def seed_sqlite_fixture(dbfile, schema_version:, tables:)
|
|
103
|
+
fixture = Sequel.sqlite(dbfile)
|
|
104
|
+
tables.each { |table| create_fixture_table(fixture, table) }
|
|
105
|
+
unless schema_version.nil?
|
|
106
|
+
create_fixture_table(fixture, :schema_info)
|
|
107
|
+
fixture[:schema_info].insert(version: schema_version)
|
|
108
|
+
end
|
|
109
|
+
ensure
|
|
110
|
+
fixture&.disconnect
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
def create_fixture_table(db, table)
|
|
114
|
+
return if db.table_exists?(table)
|
|
115
|
+
|
|
116
|
+
@db::TABLE_DEFINITIONS.fetch(table).call(db)
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
def fixture_tables_through(version)
|
|
120
|
+
@db::SCHEMA_MIGRATIONS.values_at(*(1..version)).flatten
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
def expect_current_schema_after_init
|
|
124
|
+
@db.init
|
|
125
|
+
expect(@db.schema_version).to eq(@db::SCHEMA_VERSION)
|
|
126
|
+
expect(@db.status[:tables].values).to all(eq(true))
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
it 'declares ordered migrations through the current schema version' do
|
|
130
|
+
expect(@db.migration_versions).to eq((1..@db::SCHEMA_VERSION).to_a)
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
it 'exposes schema definitions from the schema migration module' do
|
|
134
|
+
expect(@db::SCHEMA_VERSION).to eq(@db::SchemaMigrations::SCHEMA_VERSION)
|
|
135
|
+
expect(@db::TABLE_DEFINITIONS).to equal(@db::SchemaMigrations::TABLE_DEFINITIONS)
|
|
136
|
+
expect(@db::SCHEMA_MIGRATIONS).to equal(@db::SchemaMigrations::SCHEMA_MIGRATIONS)
|
|
137
|
+
expect(@db::INDEX_DEFINITIONS).to equal(@db::SchemaMigrations::INDEX_DEFINITIONS)
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
it 'applies schema migrations one version at a time' do
|
|
141
|
+
with_sqlite_fixture(schema_version: 1, tables: %i[hosts groups schema_info]) do
|
|
142
|
+
@db.init_sqlite3
|
|
143
|
+
|
|
144
|
+
expect(@db).to receive(:apply_schema_migration!).ordered.with(1).and_call_original
|
|
145
|
+
expect(@db).to receive(:apply_schema_migration!).ordered.with(2).and_call_original
|
|
146
|
+
expect(@db).to receive(:apply_schema_migration!).ordered.with(3).and_call_original
|
|
147
|
+
expect(@db).to receive(:apply_schema_migration!).ordered.with(4).and_call_original
|
|
148
|
+
|
|
149
|
+
@db.migrate_schema!
|
|
150
|
+
|
|
151
|
+
expect(@db.schema_version).to eq(@db::SCHEMA_VERSION)
|
|
152
|
+
expect(@db.status[:tables][:audit_events]).to eq(true)
|
|
153
|
+
expect(@db.status[:tables][:tags]).to eq(true)
|
|
154
|
+
end
|
|
155
|
+
end
|
|
156
|
+
|
|
157
|
+
it 'creates unique and lookup indexes in schema version 4' do
|
|
158
|
+
expected = {
|
|
159
|
+
hostvars: :idx_hostvars_host_id_name,
|
|
160
|
+
groupvars: :idx_groupvars_group_id_name,
|
|
161
|
+
groups_hosts: :idx_groups_hosts_host_id_group_id,
|
|
162
|
+
groups_groups: :idx_groups_groups_parent_id_child_id,
|
|
163
|
+
hosts_tags: :idx_hosts_tags_host_id_tag_id,
|
|
164
|
+
groups_tags: :idx_groups_tags_group_id_tag_id
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
expected.each do |table, index_name|
|
|
168
|
+
expect(@db.db.indexes(table)).to include(index_name)
|
|
169
|
+
expect(@db.db.indexes(table).fetch(index_name)[:unique]).to eq(true)
|
|
170
|
+
end
|
|
171
|
+
end
|
|
172
|
+
|
|
173
|
+
it 'enforces unique host variable names per host' do
|
|
174
|
+
@db.reset
|
|
175
|
+
host = @db.models[:host].create(name: 'indexed-host')
|
|
176
|
+
@db.db[:hostvars].insert(host_id: host.id, name: 'env', value: 'prod')
|
|
177
|
+
|
|
178
|
+
expect do
|
|
179
|
+
@db.db[:hostvars].insert(host_id: host.id, name: 'env', value: 'prod')
|
|
180
|
+
end.to raise_error(Sequel::UniqueConstraintViolation)
|
|
181
|
+
end
|
|
182
|
+
|
|
183
|
+
it 'enforces unique host-group associations' do
|
|
184
|
+
@db.reset
|
|
185
|
+
host = @db.models[:host].create(name: 'indexed-host')
|
|
186
|
+
group = @db.models[:group].create(name: 'indexed-group')
|
|
187
|
+
@db.db[:groups_hosts].insert(host_id: host.id, group_id: group.id)
|
|
188
|
+
|
|
189
|
+
expect do
|
|
190
|
+
@db.db[:groups_hosts].insert(host_id: host.id, group_id: group.id)
|
|
191
|
+
end.to raise_error(Sequel::UniqueConstraintViolation)
|
|
192
|
+
end
|
|
193
|
+
|
|
194
|
+
it 'removes duplicate rows with identical values before adding unique indexes' do
|
|
195
|
+
with_sqlite_fixture(schema_version: 3, tables: fixture_tables_through(3)) do
|
|
196
|
+
@db.init_sqlite3
|
|
197
|
+
host_id = @db.db[:hosts].insert(name: 'dup-host')
|
|
198
|
+
group_id = @db.db[:groups].insert(name: 'dup-group')
|
|
199
|
+
@db.db[:hostvars].insert(host_id: host_id, name: 'env', value: 'prod')
|
|
200
|
+
@db.db[:hostvars].insert(host_id: host_id, name: 'env', value: 'prod')
|
|
201
|
+
@db.db[:groups_hosts].insert(host_id: host_id, group_id: group_id)
|
|
202
|
+
@db.db[:groups_hosts].insert(host_id: host_id, group_id: group_id)
|
|
203
|
+
|
|
204
|
+
@db.migrate_schema!
|
|
205
|
+
|
|
206
|
+
expect(@db.schema_version).to eq(@db::SCHEMA_VERSION)
|
|
207
|
+
expect(@db.db[:hostvars].where(host_id: host_id, name: 'env').count).to eq(1)
|
|
208
|
+
expect(@db.db[:groups_hosts].where(host_id: host_id, group_id: group_id).count).to eq(1)
|
|
209
|
+
end
|
|
210
|
+
end
|
|
211
|
+
|
|
212
|
+
it 'refuses index migration when duplicate variable rows have conflicting values' do
|
|
213
|
+
with_sqlite_fixture(schema_version: 3, tables: fixture_tables_through(3)) do
|
|
214
|
+
@db.init_sqlite3
|
|
215
|
+
host_id = @db.db[:hosts].insert(name: 'conflict-host')
|
|
216
|
+
@db.db[:hostvars].insert(host_id: host_id, name: 'env', value: 'prod')
|
|
217
|
+
@db.db[:hostvars].insert(host_id: host_id, name: 'env', value: 'dev')
|
|
218
|
+
|
|
219
|
+
expect { @db.migrate_schema! }.to raise_error(
|
|
220
|
+
Moose::Inventory::DB::MooseDBException,
|
|
221
|
+
/conflicting duplicates/
|
|
222
|
+
)
|
|
223
|
+
end
|
|
224
|
+
end
|
|
225
|
+
|
|
226
|
+
it 'upgrades a pre-schema-info sqlite database by creating missing additive tables' do
|
|
227
|
+
with_sqlite_fixture(tables: %i[hosts groups]) do
|
|
228
|
+
expect_current_schema_after_init
|
|
229
|
+
end
|
|
230
|
+
end
|
|
231
|
+
|
|
232
|
+
it 'upgrades a schema version 1 sqlite database to the current schema' do
|
|
233
|
+
with_sqlite_fixture(schema_version: 1, tables: %i[hosts groups]) do
|
|
234
|
+
expect_current_schema_after_init
|
|
235
|
+
end
|
|
236
|
+
end
|
|
237
|
+
|
|
238
|
+
it 'upgrades a schema version 2 sqlite database to the current schema' do
|
|
239
|
+
with_sqlite_fixture(schema_version: 2, tables: %i[hosts groups schema_info]) do
|
|
240
|
+
expect_current_schema_after_init
|
|
241
|
+
end
|
|
242
|
+
end
|
|
243
|
+
|
|
244
|
+
it 'opens a current sqlite database without changing its schema version' do
|
|
245
|
+
with_sqlite_fixture(schema_version: @db::SCHEMA_VERSION, tables: %i[hosts groups schema_info tags]) do
|
|
246
|
+
expect_current_schema_after_init
|
|
247
|
+
end
|
|
248
|
+
end
|
|
249
|
+
|
|
250
|
+
it 'refuses to open a database from a future schema version' do
|
|
251
|
+
with_sqlite_fixture(schema_version: @db::SCHEMA_VERSION + 1, tables: %i[hosts groups schema_info]) do
|
|
252
|
+
expect { @db.init }.to raise_error(
|
|
253
|
+
Moose::Inventory::DB::MooseDBException,
|
|
254
|
+
/newer than supported version #{@db::SCHEMA_VERSION}/
|
|
255
|
+
)
|
|
256
|
+
end
|
|
257
|
+
end
|
|
258
|
+
|
|
259
|
+
it 'refuses to migrate a database from a future schema version' do
|
|
260
|
+
with_sqlite_fixture(schema_version: @db::SCHEMA_VERSION + 1, tables: %i[hosts groups schema_info]) do
|
|
261
|
+
@db.init_sqlite3
|
|
262
|
+
|
|
263
|
+
expect { @db.migrate! }.to raise_error(
|
|
264
|
+
Moose::Inventory::DB::MooseDBException,
|
|
265
|
+
/newer than supported version #{@db::SCHEMA_VERSION}/
|
|
266
|
+
)
|
|
267
|
+
end
|
|
268
|
+
end
|
|
269
|
+
|
|
270
|
+
it 'repairs dirty partial schemas during startup without changing the final schema version' do
|
|
271
|
+
with_sqlite_fixture(schema_version: @db::SCHEMA_VERSION, tables: %i[hosts groups schema_info]) do
|
|
272
|
+
@db.init
|
|
273
|
+
|
|
274
|
+
status = @db.status
|
|
275
|
+
|
|
276
|
+
expect(status[:schema_version]).to eq(@db::SCHEMA_VERSION)
|
|
277
|
+
expect(status[:tables].values).to all(eq(true))
|
|
278
|
+
end
|
|
279
|
+
end
|
|
280
|
+
end
|
|
99
281
|
describe '.init_exceptions()' do
|
|
100
282
|
it 'is responsive' do
|
|
101
283
|
expect(@db.respond_to?(:init_exceptions)).to eq(true)
|
|
102
284
|
end
|
|
103
285
|
end
|
|
104
286
|
|
|
287
|
+
describe '.reset_runtime_state()' do
|
|
288
|
+
it 'clears cached db, models, and exceptions' do
|
|
289
|
+
saved_db = @db.instance_variable_get(:@db)
|
|
290
|
+
saved_models = @db.instance_variable_get(:@models)
|
|
291
|
+
saved_exceptions = @db.instance_variable_get(:@exceptions)
|
|
292
|
+
|
|
293
|
+
@db.instance_variable_set(:@db, :fake_db)
|
|
294
|
+
@db.instance_variable_set(:@models, { fake: true })
|
|
295
|
+
@db.instance_variable_set(:@exceptions, { fake: true })
|
|
296
|
+
|
|
297
|
+
@db.reset_runtime_state
|
|
298
|
+
|
|
299
|
+
expect(@db.db).to be_nil
|
|
300
|
+
expect(@db.models).to be_nil
|
|
301
|
+
expect(@db.exceptions).to be_nil
|
|
302
|
+
|
|
303
|
+
@db.instance_variable_set(:@db, saved_db)
|
|
304
|
+
@db.instance_variable_set(:@models, saved_models)
|
|
305
|
+
@db.instance_variable_set(:@exceptions, saved_exceptions)
|
|
306
|
+
end
|
|
307
|
+
end
|
|
308
|
+
|
|
105
309
|
describe '.connect()' do
|
|
106
310
|
it 'dispatches the documented sqlite3 adapter to the sqlite initializer' do
|
|
107
311
|
with_db_config(adapter: 'sqlite3') do
|
|
@@ -146,18 +350,20 @@ RSpec.describe 'Moose::Inventory::DB' do
|
|
|
146
350
|
|
|
147
351
|
it 'creates nested parent directories for configured database files' do
|
|
148
352
|
saved_db = @db.instance_variable_get(:@db)
|
|
353
|
+
saved_models = @db.instance_variable_get(:@models)
|
|
354
|
+
saved_exceptions = @db.instance_variable_get(:@exceptions)
|
|
149
355
|
saved_settings = @config._settings.dup
|
|
150
356
|
tmpdir = Dir.mktmpdir('moose-inventory-sqlite')
|
|
151
357
|
nested_dbfile = File.join(tmpdir, 'one', 'two', 'inventory.db')
|
|
152
358
|
|
|
153
359
|
begin
|
|
154
|
-
@db.
|
|
360
|
+
@db.reset_runtime_state
|
|
155
361
|
@config._settings.clear
|
|
156
362
|
@config._settings[:config] = {
|
|
157
363
|
db: {
|
|
158
364
|
adapter: 'sqlite3',
|
|
159
|
-
file: nested_dbfile
|
|
160
|
-
}
|
|
365
|
+
file: nested_dbfile
|
|
366
|
+
}
|
|
161
367
|
}
|
|
162
368
|
|
|
163
369
|
@db.init_sqlite3
|
|
@@ -167,7 +373,10 @@ RSpec.describe 'Moose::Inventory::DB' do
|
|
|
167
373
|
ensure
|
|
168
374
|
current_db = @db.instance_variable_get(:@db)
|
|
169
375
|
current_db.disconnect if current_db.respond_to?(:disconnect)
|
|
376
|
+
@db.reset_runtime_state
|
|
170
377
|
@db.instance_variable_set(:@db, saved_db)
|
|
378
|
+
@db.instance_variable_set(:@models, saved_models)
|
|
379
|
+
@db.instance_variable_set(:@exceptions, saved_exceptions)
|
|
171
380
|
@config._settings.clear
|
|
172
381
|
@config._settings.merge!(saved_settings)
|
|
173
382
|
FileUtils.remove_entry(tmpdir) if tmpdir && Dir.exist?(tmpdir)
|
|
@@ -190,6 +399,59 @@ RSpec.describe 'Moose::Inventory::DB' do
|
|
|
190
399
|
end
|
|
191
400
|
end
|
|
192
401
|
|
|
402
|
+
it 'raises a Moose DB exception when password and password_env are missing' do
|
|
403
|
+
with_db_config(
|
|
404
|
+
adapter: 'mysql',
|
|
405
|
+
host: 'localhost',
|
|
406
|
+
database: 'moose_inventory_test',
|
|
407
|
+
user: 'moose'
|
|
408
|
+
) do
|
|
409
|
+
expect { @db.init_mysql }.to raise_error(
|
|
410
|
+
Moose::Inventory::DB::MooseDBException,
|
|
411
|
+
/Expected key password or password_env missing in mysql configuration/
|
|
412
|
+
)
|
|
413
|
+
end
|
|
414
|
+
end
|
|
415
|
+
|
|
416
|
+
it 'uses a mysql password from the configured environment variable' do
|
|
417
|
+
saved_db = @db.instance_variable_get(:@db)
|
|
418
|
+
saved_settings = @config._settings.dup
|
|
419
|
+
saved_password = ENV.fetch('MOOSE_INVENTORY_MYSQL_PASSWORD', nil)
|
|
420
|
+
mysql_config = {
|
|
421
|
+
adapter: 'mysql',
|
|
422
|
+
host: 'localhost',
|
|
423
|
+
database: 'moose_inventory_test',
|
|
424
|
+
user: 'moose',
|
|
425
|
+
password_env: 'MOOSE_INVENTORY_MYSQL_PASSWORD'
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
begin
|
|
429
|
+
ENV['MOOSE_INVENTORY_MYSQL_PASSWORD'] = 'env-secret'
|
|
430
|
+
@db.instance_variable_set(:@db, nil)
|
|
431
|
+
@config._settings.clear
|
|
432
|
+
@config._settings[:config] = { db: mysql_config }
|
|
433
|
+
|
|
434
|
+
expect(Sequel).to receive(:mysql2).with(
|
|
435
|
+
user: 'moose',
|
|
436
|
+
password: 'env-secret',
|
|
437
|
+
host: 'localhost',
|
|
438
|
+
database: 'moose_inventory_test'
|
|
439
|
+
).and_return(:mysql2_connection)
|
|
440
|
+
|
|
441
|
+
@db.init_mysql
|
|
442
|
+
expect(@db.db).to eq(:mysql2_connection)
|
|
443
|
+
ensure
|
|
444
|
+
if saved_password.nil?
|
|
445
|
+
ENV.delete('MOOSE_INVENTORY_MYSQL_PASSWORD')
|
|
446
|
+
else
|
|
447
|
+
ENV['MOOSE_INVENTORY_MYSQL_PASSWORD'] = saved_password
|
|
448
|
+
end
|
|
449
|
+
@db.instance_variable_set(:@db, saved_db)
|
|
450
|
+
@config._settings.clear
|
|
451
|
+
@config._settings.merge!(saved_settings)
|
|
452
|
+
end
|
|
453
|
+
end
|
|
454
|
+
|
|
193
455
|
it 'uses the mysql2 Sequel adapter with configured connection settings' do
|
|
194
456
|
saved_db = @db.instance_variable_get(:@db)
|
|
195
457
|
saved_settings = @config._settings.dup
|
|
@@ -198,7 +460,7 @@ RSpec.describe 'Moose::Inventory::DB' do
|
|
|
198
460
|
host: 'localhost',
|
|
199
461
|
database: 'moose_inventory_test',
|
|
200
462
|
user: 'moose',
|
|
201
|
-
password: 'secret'
|
|
463
|
+
password: 'secret'
|
|
202
464
|
}
|
|
203
465
|
|
|
204
466
|
begin
|
|
@@ -238,6 +500,67 @@ RSpec.describe 'Moose::Inventory::DB' do
|
|
|
238
500
|
end
|
|
239
501
|
end
|
|
240
502
|
|
|
503
|
+
it 'raises a Moose DB exception when password_env points to an unset variable' do
|
|
504
|
+
saved_password = ENV.fetch('MOOSE_INVENTORY_POSTGRES_PASSWORD', nil)
|
|
505
|
+
ENV.delete('MOOSE_INVENTORY_POSTGRES_PASSWORD')
|
|
506
|
+
|
|
507
|
+
begin
|
|
508
|
+
with_db_config(
|
|
509
|
+
adapter: 'postgresql',
|
|
510
|
+
host: 'localhost',
|
|
511
|
+
database: 'moose_inventory_test',
|
|
512
|
+
user: 'moose',
|
|
513
|
+
password_env: 'MOOSE_INVENTORY_POSTGRES_PASSWORD'
|
|
514
|
+
) do
|
|
515
|
+
expect { @db.init_postgresql }.to raise_error(
|
|
516
|
+
Moose::Inventory::DB::MooseDBException,
|
|
517
|
+
/Environment variable MOOSE_INVENTORY_POSTGRES_PASSWORD is not set for postgresql password/
|
|
518
|
+
)
|
|
519
|
+
end
|
|
520
|
+
ensure
|
|
521
|
+
ENV['MOOSE_INVENTORY_POSTGRES_PASSWORD'] = saved_password unless saved_password.nil?
|
|
522
|
+
end
|
|
523
|
+
end
|
|
524
|
+
|
|
525
|
+
it 'uses a postgresql password from the configured environment variable' do
|
|
526
|
+
saved_db = @db.instance_variable_get(:@db)
|
|
527
|
+
saved_settings = @config._settings.dup
|
|
528
|
+
saved_password = ENV.fetch('MOOSE_INVENTORY_POSTGRES_PASSWORD', nil)
|
|
529
|
+
postgresql_config = {
|
|
530
|
+
adapter: 'postgresql',
|
|
531
|
+
host: 'localhost',
|
|
532
|
+
database: 'moose_inventory_test',
|
|
533
|
+
user: 'moose',
|
|
534
|
+
password_env: 'MOOSE_INVENTORY_POSTGRES_PASSWORD'
|
|
535
|
+
}
|
|
536
|
+
|
|
537
|
+
begin
|
|
538
|
+
ENV['MOOSE_INVENTORY_POSTGRES_PASSWORD'] = 'env-secret'
|
|
539
|
+
@db.instance_variable_set(:@db, nil)
|
|
540
|
+
@config._settings.clear
|
|
541
|
+
@config._settings[:config] = { db: postgresql_config }
|
|
542
|
+
|
|
543
|
+
expect(Sequel).to receive(:postgres).with(
|
|
544
|
+
user: 'moose',
|
|
545
|
+
password: 'env-secret',
|
|
546
|
+
host: 'localhost',
|
|
547
|
+
database: 'moose_inventory_test'
|
|
548
|
+
).and_return(:postgresql_connection)
|
|
549
|
+
|
|
550
|
+
@db.init_postgresql
|
|
551
|
+
expect(@db.db).to eq(:postgresql_connection)
|
|
552
|
+
ensure
|
|
553
|
+
if saved_password.nil?
|
|
554
|
+
ENV.delete('MOOSE_INVENTORY_POSTGRES_PASSWORD')
|
|
555
|
+
else
|
|
556
|
+
ENV['MOOSE_INVENTORY_POSTGRES_PASSWORD'] = saved_password
|
|
557
|
+
end
|
|
558
|
+
@db.instance_variable_set(:@db, saved_db)
|
|
559
|
+
@config._settings.clear
|
|
560
|
+
@config._settings.merge!(saved_settings)
|
|
561
|
+
end
|
|
562
|
+
end
|
|
563
|
+
|
|
241
564
|
it 'uses the postgres Sequel adapter with configured connection settings' do
|
|
242
565
|
saved_db = @db.instance_variable_get(:@db)
|
|
243
566
|
saved_settings = @config._settings.dup
|
|
@@ -246,7 +569,7 @@ RSpec.describe 'Moose::Inventory::DB' do
|
|
|
246
569
|
host: 'localhost',
|
|
247
570
|
database: 'moose_inventory_test',
|
|
248
571
|
user: 'moose',
|
|
249
|
-
password: 'secret'
|
|
572
|
+
password: 'secret'
|
|
250
573
|
}
|
|
251
574
|
|
|
252
575
|
begin
|
|
@@ -327,6 +650,70 @@ RSpec.describe 'Moose::Inventory::DB' do
|
|
|
327
650
|
end
|
|
328
651
|
end
|
|
329
652
|
|
|
653
|
+
describe '.busy_retry_delay()' do
|
|
654
|
+
it 'uses deterministic capped exponential backoff delays' do
|
|
655
|
+
expect(@db.busy_retry_delay(1)).to eq(0.05)
|
|
656
|
+
expect(@db.busy_retry_delay(2)).to eq(0.1)
|
|
657
|
+
expect(@db.busy_retry_delay(5)).to eq(0.8)
|
|
658
|
+
expect(@db.busy_retry_delay(6)).to eq(1.0)
|
|
659
|
+
expect(@db.busy_retry_delay(10)).to eq(1.0)
|
|
660
|
+
end
|
|
661
|
+
end
|
|
662
|
+
|
|
663
|
+
describe '.busy_database_error?()' do
|
|
664
|
+
it 'identifies Sequel busy database errors by message' do
|
|
665
|
+
expect(@db.busy_database_error?(Sequel::DatabaseError.new('BusyException: locked'))).to eq(true)
|
|
666
|
+
expect(@db.busy_database_error?(Sequel::DatabaseError.new('other database failure'))).to eq(false)
|
|
667
|
+
end
|
|
668
|
+
end
|
|
669
|
+
|
|
670
|
+
describe '.retry_busy_transaction()' do
|
|
671
|
+
it 'sleeps for the deterministic retry delay before another busy retry' do
|
|
672
|
+
delays = []
|
|
673
|
+
error = Sequel::DatabaseError.new('BusyException: database is locked')
|
|
674
|
+
|
|
675
|
+
@db.retry_busy_transaction(error, 3, sleeper: ->(delay) { delays << delay })
|
|
676
|
+
|
|
677
|
+
expect(delays).to eq([0.2])
|
|
678
|
+
end
|
|
679
|
+
|
|
680
|
+
it 'raises the original error when the retry limit is exceeded' do
|
|
681
|
+
error = Sequel::DatabaseError.new('BusyException: database is locked')
|
|
682
|
+
|
|
683
|
+
actual_stderr = capture(:STDERR) do
|
|
684
|
+
expect { @db.retry_busy_transaction(error, 11, sleeper: ->(_delay) {}) }
|
|
685
|
+
.to raise_error(error)
|
|
686
|
+
end
|
|
687
|
+
|
|
688
|
+
expect(actual_stderr).to include('The database appears to be locked by another process')
|
|
689
|
+
end
|
|
690
|
+
end
|
|
691
|
+
|
|
692
|
+
describe '.purge()' do
|
|
693
|
+
it 'uses drop_table for non-sqlite adapters' do
|
|
694
|
+
saved_db = @db.instance_variable_get(:@db)
|
|
695
|
+
|
|
696
|
+
begin
|
|
697
|
+
fake_db = instance_double('DB')
|
|
698
|
+
@db.instance_variable_set(:@db, fake_db)
|
|
699
|
+
allow(@db).to receive(:sqlite_adapter?).and_return(false)
|
|
700
|
+
expect(fake_db).to receive(:drop_table).with(
|
|
701
|
+
:hosts,
|
|
702
|
+
:hostvars,
|
|
703
|
+
:groups,
|
|
704
|
+
:groupvars,
|
|
705
|
+
:group_hosts,
|
|
706
|
+
if_exists: true,
|
|
707
|
+
cascade: true
|
|
708
|
+
)
|
|
709
|
+
|
|
710
|
+
@db.purge
|
|
711
|
+
ensure
|
|
712
|
+
@db.instance_variable_set(:@db, saved_db)
|
|
713
|
+
end
|
|
714
|
+
end
|
|
715
|
+
end
|
|
716
|
+
|
|
330
717
|
describe '.reset()' do
|
|
331
718
|
it 'should be responsive' do
|
|
332
719
|
result = @db.respond_to?(:reset)
|
|
@@ -341,7 +728,6 @@ RSpec.describe 'Moose::Inventory::DB' do
|
|
|
341
728
|
# Reset the DB
|
|
342
729
|
@db.reset
|
|
343
730
|
|
|
344
|
-
#
|
|
345
731
|
hosts = @db.models[:host].all
|
|
346
732
|
expect(hosts.count).to eq(0)
|
|
347
733
|
|
|
@@ -360,6 +746,57 @@ RSpec.describe 'Moose::Inventory::DB' do
|
|
|
360
746
|
expect(result).to eq(true)
|
|
361
747
|
end
|
|
362
748
|
|
|
749
|
+
it 'retries busy database transactions before succeeding' do
|
|
750
|
+
saved_db = @db.instance_variable_get(:@db)
|
|
751
|
+
fake_db = Class.new do
|
|
752
|
+
attr_reader :attempts
|
|
753
|
+
|
|
754
|
+
def initialize(error)
|
|
755
|
+
@error = error
|
|
756
|
+
@attempts = 0
|
|
757
|
+
end
|
|
758
|
+
|
|
759
|
+
def transaction(savepoint:)
|
|
760
|
+
raise ArgumentError, 'expected savepoint' unless savepoint
|
|
761
|
+
|
|
762
|
+
@attempts += 1
|
|
763
|
+
raise @error if @attempts == 1
|
|
764
|
+
|
|
765
|
+
yield
|
|
766
|
+
end
|
|
767
|
+
end.new(Sequel::DatabaseError.new('BusyException: database is locked'))
|
|
768
|
+
|
|
769
|
+
begin
|
|
770
|
+
@db.instance_variable_set(:@db, fake_db)
|
|
771
|
+
allow(@db).to receive(:retry_busy_transaction)
|
|
772
|
+
|
|
773
|
+
result = @db.transaction { :ok }
|
|
774
|
+
|
|
775
|
+
expect(result).to eq(:ok)
|
|
776
|
+
expect(fake_db.attempts).to eq(2)
|
|
777
|
+
expect(@db).to have_received(:retry_busy_transaction).once
|
|
778
|
+
ensure
|
|
779
|
+
@db.instance_variable_set(:@db, saved_db)
|
|
780
|
+
end
|
|
781
|
+
end
|
|
782
|
+
|
|
783
|
+
it 're-raises non-busy database errors without retrying' do
|
|
784
|
+
saved_db = @db.instance_variable_get(:@db)
|
|
785
|
+
error = Sequel::DatabaseError.new('constraint failed')
|
|
786
|
+
fake_db = instance_double('DB')
|
|
787
|
+
|
|
788
|
+
begin
|
|
789
|
+
@db.instance_variable_set(:@db, fake_db)
|
|
790
|
+
allow(fake_db).to receive(:transaction).and_raise(error)
|
|
791
|
+
allow(@db).to receive(:retry_busy_transaction)
|
|
792
|
+
|
|
793
|
+
expect { @db.transaction { :ignored } }.to raise_error(error)
|
|
794
|
+
expect(@db).not_to have_received(:retry_busy_transaction)
|
|
795
|
+
ensure
|
|
796
|
+
@db.instance_variable_set(:@db, saved_db)
|
|
797
|
+
end
|
|
798
|
+
end
|
|
799
|
+
|
|
363
800
|
it 'should perform transactions' do
|
|
364
801
|
hosts = @db.models[:host].all
|
|
365
802
|
count = { initial: hosts.count, items: 0 }
|
|
@@ -391,7 +828,7 @@ RSpec.describe 'Moose::Inventory::DB' do
|
|
|
391
828
|
count[:items] = count[:items] + 1
|
|
392
829
|
@db.models[:host].create(name: "rollback-#{count[:items]}")
|
|
393
830
|
end
|
|
394
|
-
|
|
831
|
+
raise Sequel::Rollback, 'Test error'
|
|
395
832
|
end
|
|
396
833
|
|
|
397
834
|
hosts = @db.models[:host].all
|
|
@@ -402,5 +839,90 @@ RSpec.describe 'Moose::Inventory::DB' do
|
|
|
402
839
|
|
|
403
840
|
expect(count[:final]).to eq(count[:initial])
|
|
404
841
|
end
|
|
842
|
+
|
|
843
|
+
it 'prints concise Moose DB transaction errors by default' do
|
|
844
|
+
saved_trace = @config._confopts[:trace]
|
|
845
|
+
@config._confopts[:trace] = false
|
|
846
|
+
|
|
847
|
+
begin
|
|
848
|
+
actual = runner do
|
|
849
|
+
@db.transaction do
|
|
850
|
+
raise @db.exceptions[:moose], 'Trace regression target'
|
|
851
|
+
end
|
|
852
|
+
end
|
|
853
|
+
ensure
|
|
854
|
+
@config._confopts[:trace] = saved_trace
|
|
855
|
+
end
|
|
856
|
+
|
|
857
|
+
expect(actual[:unexpected]).to eq(false)
|
|
858
|
+
expect(actual[:aborted]).to eq(true)
|
|
859
|
+
expect(actual[:STDERR]).to eq(
|
|
860
|
+
"An error occurred during a transaction, any changes have been rolled back.\n" \
|
|
861
|
+
"ERROR: Trace regression target\n"
|
|
862
|
+
)
|
|
863
|
+
end
|
|
864
|
+
|
|
865
|
+
it 'prints the Moose DB exception backtrace when trace is enabled' do
|
|
866
|
+
saved_trace = @config._confopts[:trace]
|
|
867
|
+
@config._confopts[:trace] = true
|
|
868
|
+
|
|
869
|
+
begin
|
|
870
|
+
actual = runner do
|
|
871
|
+
@db.transaction do
|
|
872
|
+
raise @db.exceptions[:moose], 'Trace regression target'
|
|
873
|
+
end
|
|
874
|
+
end
|
|
875
|
+
ensure
|
|
876
|
+
@config._confopts[:trace] = saved_trace
|
|
877
|
+
end
|
|
878
|
+
|
|
879
|
+
expect(actual[:unexpected]).to eq(false)
|
|
880
|
+
expect(actual[:aborted]).to eq(true)
|
|
881
|
+
expect(actual[:STDERR]).to include(
|
|
882
|
+
"An error occurred during a transaction, any changes have been rolled back.\n"
|
|
883
|
+
)
|
|
884
|
+
expect(actual[:STDERR]).to include('Moose::Inventory::DB::MooseDBException')
|
|
885
|
+
expect(actual[:STDERR]).to include('Trace regression target')
|
|
886
|
+
expect(actual[:STDERR]).to include('spec/lib/moose_inventory/db/db_spec.rb')
|
|
887
|
+
expect(actual[:STDERR]).to include("ERROR: Trace regression target\n")
|
|
888
|
+
expect(actual[:STDERR]).not_to include('NoMethodError')
|
|
889
|
+
end
|
|
890
|
+
|
|
891
|
+
it 'warns and re-raises ordinary StandardError failures' do
|
|
892
|
+
actual = runner do
|
|
893
|
+
@db.transaction do
|
|
894
|
+
raise 'generic failure'
|
|
895
|
+
end
|
|
896
|
+
end
|
|
897
|
+
|
|
898
|
+
expect(actual[:aborted]).to eq(false)
|
|
899
|
+
expect(actual[:unexpected].class).to eq(RuntimeError)
|
|
900
|
+
expect(actual[:unexpected].message).to eq('generic failure')
|
|
901
|
+
expect(actual[:STDERR]).to eq(
|
|
902
|
+
"An error occurred during a transaction, any changes have been rolled back.\n"
|
|
903
|
+
)
|
|
904
|
+
end
|
|
905
|
+
|
|
906
|
+
it 'warns and re-raises SystemExit failures triggered inside the transaction' do
|
|
907
|
+
actual = runner do
|
|
908
|
+
@db.transaction do
|
|
909
|
+
raise SystemExit.new(7), 'stop now'
|
|
910
|
+
end
|
|
911
|
+
end
|
|
912
|
+
|
|
913
|
+
expect(actual[:unexpected]).to eq(false)
|
|
914
|
+
expect(actual[:aborted]).to eq(true)
|
|
915
|
+
expect(actual[:STDERR]).to eq(
|
|
916
|
+
"An error occurred during a transaction, any changes have been rolled back.\n"
|
|
917
|
+
)
|
|
918
|
+
end
|
|
919
|
+
|
|
920
|
+
it 'does not swallow non-rescued fatal exceptions such as interrupts' do
|
|
921
|
+
expect do
|
|
922
|
+
@db.transaction do
|
|
923
|
+
raise Interrupt, 'ctrl-c'
|
|
924
|
+
end
|
|
925
|
+
end.to raise_error(Interrupt, 'ctrl-c')
|
|
926
|
+
end
|
|
405
927
|
end
|
|
406
928
|
end
|