spec/keepassx/database_spec.rb in keepassx-0.1.0 vs spec/keepassx/database_spec.rb in keepassx-1.0.0

- old
+ new

@@ -1,56 +1,518 @@ require 'spec_helper' describe Keepassx::Database do - describe 'self.open' do - it "creates a new instance of the databse with the file" do - db = Keepassx::Database.open(TEST_DATABASE_PATH) - db.should_not be_nil + + GROUPS_COUNT = 5 + ENTRIES_COUNT = 5 + + let(:data_array) { YAML.load(File.read(File.join(FIXTURE_PATH, 'test_data_array.yml'))) } + let(:data_array_dumped) { File.read(File.join(FIXTURE_PATH, 'test_data_array_dumped.yml')) } + + let(:test_db) { Keepassx::Database.new(TEST_DATABASE_PATH) } + let(:test_db_dumped) { File.read(File.join(FIXTURE_PATH, 'database_test_dumped.yml')) } + + let(:empty_db) { Keepassx::Database.new(EMPTY_DATABASE_PATH) } + let(:test_group) { build(:group) } + let(:test_entry) { build(:entry) } + + let(:keyfile_db) { Keepassx::Database.new(KEYFILE_DATABASE_PATH) } + let(:keyfile) { File.join(FIXTURE_PATH, 'database_with_key.key') } + let(:keyfile2) { File.join(FIXTURE_PATH, 'database_with_key2.key') } + + describe 'empty database' do + before :each do + empty_db.unlock('test') end + + it 'should have 2 groups' do + expect(empty_db.groups.size).to eq 2 + expect(empty_db.groups.map(&:name).sort).to eq ['Internet', 'eMail'] + end + + it 'should have 2 special entries' do + expect(empty_db.entries.size).to eq 2 + expect(empty_db.entries.map(&:name).sort).to eq ['Meta-Info', 'Meta-Info'] + end + + it 'should have 1 KPX_CUSTOM_ICONS_4 entry' do + entry = empty_db.find_entry(notes: 'KPX_CUSTOM_ICONS_4') + expect(entry).to be_a(Keepassx::Entry) + expect(entry.notes).to eq 'KPX_CUSTOM_ICONS_4' + end + + it 'should have 1 KPX_GROUP_TREE_STATE entry' do + entry = empty_db.find_entry(notes: 'KPX_GROUP_TREE_STATE') + expect(entry).to be_a(Keepassx::Entry) + expect(entry.notes).to eq 'KPX_GROUP_TREE_STATE' + end end - describe "unlock" do - before :each do - @db = Keepassx::Database.open(TEST_DATABASE_PATH) - @db.should be_valid + + describe '.new' do + context 'when database is instanciated from file' do + let(:test_db) { described_class.new(File.open(TEST_DATABASE_PATH)) } + + before :each do + test_db.unlock('testmasterpassword') + end + + it 'properly initialized from file' do + expect { test_db }.to_not raise_error + end + + it 'should have valid headers' do + expect(test_db.valid?).to be true + end + + it 'should have valid length' do + expect(test_db.length).to eq 1457 + end + + it 'should have valid encryption_type headers' do + expect(test_db.header.encryption_type).to eq 'SHA2' + end + + it 'has groups_count counter properly set' do + expect(test_db.header.groups_count).to eq GROUPS_COUNT + end + + it 'has entries_count counter properly set' do + expect(test_db.header.entries_count).to eq ENTRIES_COUNT + end + + it 'contains proper number of test groups' do + expect(test_db.groups.length).to eq GROUPS_COUNT + end + + it 'contains proper number of test entries' do + expect(test_db.entries.length).to eq ENTRIES_COUNT + end + + it 'preserves original data' do + expect(test_db.to_yaml(skip_date: true)).to eq test_db_dumped + end end - it "returns true when the master password is correct" do - @db.unlock('testmasterpassword').should be_true + context 'when database is instanciated from string' do + let(:test_db) { described_class.new(TEST_DATABASE_PATH) } + + before :each do + test_db.unlock('testmasterpassword') + end + + it 'properly initialized from string' do + expect { test_db }.to_not raise_error + end + + it 'should have valid headers' do + expect(test_db.valid?).to be true + end + + it 'should have valid length' do + expect(test_db.length).to eq 1457 + end + + it 'should have valid encryption_type headers' do + expect(test_db.header.encryption_type).to eq 'SHA2' + end + + it 'has groups_count counter properly set' do + expect(test_db.header.groups_count).to eq GROUPS_COUNT + end + + it 'has entries_count counter properly set' do + expect(test_db.header.entries_count).to eq ENTRIES_COUNT + end + + it 'contains proper number of test groups' do + expect(test_db.groups.length).to eq GROUPS_COUNT + end + + it 'contains proper number of test entries' do + expect(test_db.entries.length).to eq ENTRIES_COUNT + end + + it 'preserves original data' do + expect(test_db.to_yaml(skip_date: true)).to eq test_db_dumped + end end - it "returns false when the master password is incorrect" do - @db.unlock('bad password').should be_false + context 'when database is instanciated from array' do + let(:test_db) { described_class.new(data_array) } + + it 'properly initialized from Array' do + expect { test_db }.to_not raise_error + end + + it 'should have valid headers' do + expect(test_db.valid?).to be true + end + + it 'should have valid length' do + expect(test_db.length).to eq 2189 + end + + it 'should have valid encryption_type headers' do + expect(test_db.header.encryption_type).to eq 'SHA2' + end + + it 'has groups_count counter properly set' do + expect(test_db.header.groups_count).to eq 13 + end + + it 'has entries_count counter properly set' do + expect(test_db.header.entries_count).to eq 4 + end + + it 'contains proper number of test groups' do + expect(test_db.groups.length).to eq 13 + end + + it 'contains proper number of test entries' do + expect(test_db.entries.length).to eq 4 + end + + it 'preserves original data' do + expect(test_db.to_yaml(skip_date: true)).to eq data_array_dumped + end end end - describe "an unlocked database" do + + describe '#unlock' do + context 'when no key file is needed' do + it 'returns true when the master password is correct' do + expect(test_db.unlock('testmasterpassword')).to be true + end + + it 'returns false when the master password is incorrect' do + expect(test_db.unlock('bad password')).to be false + end + end + + context 'when a key file is needed' do + it 'returns true when the master password is correct and a valid keyfile is given' do + expect(keyfile_db.unlock('test', keyfile)).to be true + end + + it 'returns false when the master password is incorrect' do + expect(keyfile_db.unlock('bad password', keyfile)).to be false + end + + it 'returns false when the keyfile is missing' do + expect(keyfile_db.unlock('test')).to be false + end + + it 'returns false when the keyfile dont match' do + expect(keyfile_db.unlock('test', keyfile2)).to be false + end + end + end + + + describe '#locked?' do + it 'returns true when database is locked' do + expect(test_db.locked?).to be true + end + + it 'returns false when database is unlocked' do + test_db.unlock('testmasterpassword') + expect(test_db.locked?).to be false + end + end + + + describe '#to_a' do + it 'returns Array database representation' do + expect(described_class.new(data_array).to_a.class).to be Array + end + end + + + describe '#checksum' do + let(:db1) { described_class.new data_array } + let(:db2) { described_class.new data_array } + + it 'has the same checksum for the same data' do + expect(db1.checksum).to eq db2.checksum + end + end + + + context 'unlocked database' do before :each do - @db = Keepassx::Database.open(TEST_DATABASE_PATH) - @db.unlock('testmasterpassword') + test_db.unlock('testmasterpassword') end - it "can find entries by their title" do - @db.entry("test entry").password.should == "testpassword" + describe '#entries' do + it 'has entries' do + expect(test_db.entries.map(&:name).sort).to eq ['Meta-Info', 'Meta-Info', 'entry2', 'test entry', 'test entry 2'] + end end - it "can find groups" do - @db.groups.map(&:name).sort.should == ["Backup", "Internet", "eMail"] + describe '#groups' do + it 'has groups' do + expect(test_db.groups.map(&:name).sort).to eq ['Backup', 'Internet', 'Web', 'Wikipedia', 'eMail'] + end end - it "can search for entries" do - entries = @db.search "test" - entries.first.title.should == "test entry" + describe '#find_entry' do + it 'can find entries by their name' do + expect(test_db.find_entry('test entry').password).to eq 'testpassword' + expect(test_db.find_entry(name: 'test entry').creation_time).to eq Time.local(2011, 9, 3, 15, 34, 47) + expect(test_db.find_entry('foo')).to be nil + end end - it "can search for entries case-insensitively" do - entries = @db.search "TEST" - entries.first.title.should == "test entry" + describe '#find_group' do + it 'can find groups by their name' do + expect(test_db.find_group('Backup').name).to eq 'Backup' + expect(test_db.find_group('foo')).to be nil + end + + it 'has "Internet" group level properly set' do + expect(test_db.find_group('Internet').level).to eq 0 + end + + it 'has "Internet" group parent properly set' do + expect(test_db.find_group('Internet').parent).to be nil + end + + it 'has "Web" group level properly set' do + expect(test_db.find_group('Web').level).to eq 1 + end + + it 'has "Web" group parent properly set' do + expect(test_db.find_group('Web').parent).to eq test_db.find_group('Internet') + end + + it 'has "Wikipedia" group level properly set' do + expect(test_db.find_group('Wikipedia').level).to eq 2 + end + + it 'has "Wikipedia" group parent properly set' do + expect(test_db.find_group('Wikipedia').parent).to eq test_db.find_group('Web') + end + + it 'has "eMail" group level properly set' do + expect(test_db.find_group('eMail').level).to eq 0 + end + + it 'has "eMail" group parent properly set' do + expect(test_db.find_group('eMail').parent).to be nil + end end - it "will find the current values of entries with history" do - entries = @db.search "entry2" - entries.size.should == 1 - entries.first.title.should == "entry2" + describe '#find_entries' do + it 'should returns a list of entries' do + expect(test_db.find_entries).to eq test_db.entries + expect { |b| test_db.find_entries(&b) }.to yield_successive_args(*test_db.entries) + end + end + + describe '#find_groups' do + it 'should returns a list of groups' do + expect(test_db.find_groups).to eq test_db.groups + expect { |b| test_db.find_groups(&b) }.to yield_successive_args(*test_db.groups) + end + end + + describe '#search' do + it 'can search for entries' do + entries = test_db.search('test') + expect(entries.first.name).to eq 'test entry' + end + + it 'can search for entries case-insensitively' do + entries = test_db.search('TEST') + expect(entries.first.name).to eq 'test entry' + end + + # it 'will find the current values of entries with history' do + # entries = test_db.search 'entry2' + # expect(entries.size).to eq 1 + # expect(entries.first.name).to eq 'entry2' + # expect(entries.first.backup?).to be true + # end + end + + describe '#add_group' do + context 'when arg is a Keepassx::Group' do + it 'should increment groups_count' do + expect(test_db.groups.size).to eq GROUPS_COUNT + test_db.add_group(test_group) + expect(test_db.groups.size).to eq GROUPS_COUNT + 1 + expect(test_db.header.groups_count).to eq GROUPS_COUNT + 1 + end + end + + context 'when arg is a Hash of options' do + it 'should increment groups_count' do + expect(test_db.groups.size).to eq GROUPS_COUNT + test_db.add_group(attributes_for(:group)) + expect(test_db.groups.size).to eq GROUPS_COUNT + 1 + expect(test_db.header.groups_count).to eq GROUPS_COUNT + 1 + end + end + + context 'when arg is neither a Keepassx::Group or a Hash of options' do + it 'should raise an error' do + expect { test_db.add_group(nil) }.to raise_error(ArgumentError) + end + end + + context 'with nested groups' do + it 'should increment groups_count' do + expect(test_db.groups.size).to eq GROUPS_COUNT + parent_group = test_db.add_group(attributes_for(:group, id: 0, name: 'parent_group')) + expect(test_db.groups.size).to eq GROUPS_COUNT + 1 + expect(test_db.groups).to include(parent_group) + child_group = test_db.add_group(attributes_for(:group, id: 1, name: 'child_group', parent: parent_group)) + expect(test_db.groups.size).to eq GROUPS_COUNT + 2 + expect(test_db.header.groups_count).to eq GROUPS_COUNT + 2 + expect(child_group.parent).to eq parent_group + end + + it 'should increment groups_count' do + expect(test_db.groups.size).to eq GROUPS_COUNT + parent_group = test_db.add_group(attributes_for(:group, id: 0, name: 'parent_group')) + expect(test_db.groups.size).to eq GROUPS_COUNT + 1 + expect(test_db.groups).to include(parent_group) + child_group = test_db.add_group(attributes_for(:group, id: 1, name: 'child_group', parent: :parent_group)) + expect(test_db.groups.size).to eq GROUPS_COUNT + 2 + expect(test_db.header.groups_count).to eq GROUPS_COUNT + 2 + expect(child_group.parent).to eq parent_group + end + end + end + + describe '#add_entry' do + context 'when arg is a Keepassx::Entry' do + it 'should increment entries_count' do + expect(test_db.entries.size).to eq ENTRIES_COUNT + test_db.add_entry(test_entry) + expect(test_db.entries.size).to eq ENTRIES_COUNT + 1 + expect(test_db.header.entries_count).to eq ENTRIES_COUNT + 1 + end + end + + context 'when arg is a Hash of options' do + it 'should increment entries_count' do + expect(test_db.entries.size).to eq ENTRIES_COUNT + test_db.add_entry(attributes_for(:entry)) + expect(test_db.entries.size).to eq ENTRIES_COUNT + 1 + expect(test_db.header.entries_count).to eq ENTRIES_COUNT + 1 + end + end + + context 'when arg is neither a Keepassx::Group or a Hash of options' do + it 'should raise an error' do + expect { test_db.add_group(nil) }.to raise_error(ArgumentError) + end + end + end + + describe '#delete_group' do + it 'should decrement entries_count' do + group = test_db.find_group('eMail') + expect(test_db.groups.size).to eq GROUPS_COUNT + expect(test_db.header.groups_count).to eq GROUPS_COUNT + test_db.delete_group(group) + expect(test_db.groups.size).to eq GROUPS_COUNT - 1 + expect(test_db.header.groups_count).to eq GROUPS_COUNT - 1 + end + end + + describe '#delete_entry' do + it 'should decrement entries_count' do + entry = test_db.find_entry('test entry') + expect(test_db.entries.size).to eq ENTRIES_COUNT + expect(test_db.header.entries_count).to eq ENTRIES_COUNT + test_db.delete_entry(entry) + expect(test_db.entries.size).to eq ENTRIES_COUNT - 1 + expect(test_db.header.entries_count).to eq ENTRIES_COUNT - 1 + end + end + + describe '#save' do + context 'when database is saved from an existing file' do + it 'should save the database in KeePassX format' do + # Save database in /tmp to not override existing one + expect { test_db.save(path: '/tmp/keepass1.kdb') }.to_not raise_error + expect(File.exist?('/tmp/keepass1.kdb')).to be true + + # Reopen it and compare with original db + db = described_class.new('/tmp/keepass1.kdb') + expect(db.locked?).to be true + db.unlock('testmasterpassword') + expect(db.to_yaml).to eq test_db.to_yaml + + # Be sure to delete existing tmp files + expect(File.unlink('/tmp/keepass1.kdb')).to eq 1 + expect(File.exist?('/tmp/keepass1.kdb')).to be false + end + end + + context 'when database is saved from a data file' do + it 'should raise an error if path is not set' do + test_db = described_class.new(data_array) + expect { test_db.save }.to raise_error(ArgumentError) + end + + it 'should raise an error if path is not set' do + test_db = described_class.new(data_array) + expect { test_db.save(password: 'foo') }.to raise_error(ArgumentError) + end + + it 'should raise an error if password is not set' do + test_db = described_class.new(data_array) + expect { test_db.save(path: '/tmp/keepass2.kdb') }.to raise_error(ArgumentError) + end + + it 'should save the database if the path and the password are set' do + # Create new db from array of data + test_db = described_class.new(data_array) + + # Save database in /tmp + expect { test_db.save(path: '/tmp/keepass2.kdb', password: 'testmasterpassword') }.to_not raise_error + expect(File.exist?('/tmp/keepass2.kdb')).to be true + + # Reopen it and compare with original db + db = described_class.new('/tmp/keepass2.kdb') + expect(db.locked?).to be true + db.unlock('testmasterpassword') + expect(db.to_yaml).to eq test_db.to_yaml + + # Be sure to delete existing tmp files + expect(File.unlink('/tmp/keepass2.kdb')).to eq 1 + expect(File.exist?('/tmp/keepass2.kdb')).to be false + end + end + end + end + + + describe 'create database from scratch' do + it 'should allow creation of database from scratch' do + # Create a new Database object + db = described_class.new('/tmp/test_db.kdb') + # Add a group + group = db.add_group(name: 'Foo') + # Add an entry in this group + entry = db.add_entry(name: 'Bar', group: group) + # Save database + expect { db.save(password: 'testpassword') }.to_not raise_error + # Do some checks + expect(File.exist?('/tmp/test_db.kdb')).to be true + + # Reopen it and compare with original db + new_db = described_class.new('/tmp/test_db.kdb') + expect(new_db.locked?).to be true + new_db.unlock('testpassword') + expect(new_db.to_yaml(skip_date: true)).to eq db.to_yaml(skip_date: true) + + # Be sure to delete existing tmp files + expect(File.unlink('/tmp/test_db.kdb')).to eq 1 end end end