bitcoin 0.1.0 → 0.1.2

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 (64) hide show
  1. data/.rvmrc +1 -1
  2. data/Gemfile.lock +56 -4
  3. data/Guardfile +38 -0
  4. data/README.markdown +21 -2
  5. data/bin/rbcoin +8 -1
  6. data/bitcoin.gemspec +17 -5
  7. data/config/cucumber.yml +3 -3
  8. data/config/darcs.boring +121 -0
  9. data/doc/DEFINITION_OF_DONE.markdown +12 -0
  10. data/doc/HISTORY.markdown +19 -0
  11. data/{LICENCE.markdown → doc/LICENCE.markdown} +1 -1
  12. data/doc/TODO.markdown +31 -0
  13. data/doc/UBIQUITOUS_LANGUAGE.markdown +15 -0
  14. data/features/descriptions/command_help.feature +31 -0
  15. data/features/descriptions/satoshi_wallet/add_address.feature +49 -0
  16. data/features/descriptions/satoshi_wallet/show_addresses.feature +18 -0
  17. data/features/descriptions/satoshi_wallet/show_version.feature +17 -0
  18. data/features/descriptions/satoshi_wallet/subcommand_help.feature +20 -0
  19. data/features/fixtures/ABOUT_FIXTURES.markdown +6 -0
  20. data/features/fixtures/addressbook_wallet.dat +0 -0
  21. data/features/fixtures/new_wallet.dat +0 -0
  22. data/features/step_definitions/command_steps.rb +3 -0
  23. data/features/step_definitions/wallet_steps.rb +11 -0
  24. data/features/support/env.rb +8 -1
  25. data/lib/bitcoin/cli.rb +35 -0
  26. data/lib/bitcoin/commands.rb +3 -0
  27. data/lib/bitcoin/commands/help_command.rb +32 -0
  28. data/lib/bitcoin/commands/satoshi_wallet.rb +11 -0
  29. data/lib/bitcoin/commands/satoshi_wallet/add_address_command.rb +61 -0
  30. data/lib/bitcoin/commands/satoshi_wallet/show_addresses_command.rb +16 -0
  31. data/lib/bitcoin/commands/satoshi_wallet/show_version_command.rb +15 -0
  32. data/lib/bitcoin/commands/satoshi_wallet_command.rb +37 -0
  33. data/lib/bitcoin/commands/satoshi_wallet_command_environment.rb +28 -0
  34. data/lib/bitcoin/console/capturing_stream_bundle.rb +42 -0
  35. data/lib/bitcoin/console/stream_bundle.rb +21 -0
  36. data/lib/bitcoin/data_access/satoshi/bdb_satoshi_wallet_repository.rb +155 -0
  37. data/lib/bitcoin/data_access/satoshi/satoshi_version.rb +58 -0
  38. data/lib/bitcoin/data_access/satoshi/satoshi_wallet.rb +39 -0
  39. data/lib/bitcoin/domain/address_book.rb +19 -0
  40. data/lib/bitcoin/domain/bitcoin_address.rb +33 -0
  41. data/lib/bitcoin/filesystem/empty_temp_dir.rb +74 -0
  42. data/lib/bitcoin/rspec/argument_matchers.rb +1 -0
  43. data/lib/bitcoin/rspec/argument_matchers/block_evaluating_to_matcher.rb +23 -0
  44. data/lib/bitcoin/rspec/directory_helpers.rb +22 -0
  45. data/lib/bitcoin/version.rb +1 -1
  46. data/spec/bitcoin/cli_spec.rb +128 -0
  47. data/spec/bitcoin/commands/help_command_spec.rb +53 -0
  48. data/spec/bitcoin/commands/satoshi_wallet/add_address_command_spec.rb +149 -0
  49. data/spec/bitcoin/commands/satoshi_wallet/show_addresses_command_spec.rb +26 -0
  50. data/spec/bitcoin/commands/satoshi_wallet/show_version_command_spec.rb +26 -0
  51. data/spec/bitcoin/commands/satoshi_wallet_command_environment_spec.rb +76 -0
  52. data/spec/bitcoin/commands/satoshi_wallet_command_spec.rb +73 -0
  53. data/spec/bitcoin/console/_contracts/stream_bundle_contract.rb +29 -0
  54. data/spec/bitcoin/console/capturing_stream_bundle_spec.rb +74 -0
  55. data/spec/bitcoin/console/stream_bundle_spec.rb +13 -0
  56. data/spec/bitcoin/data_access/satoshi/bdb_satoshi_wallet_repository_spec.rb +78 -0
  57. data/spec/bitcoin/data_access/satoshi/satoshi_version_spec.rb +112 -0
  58. data/spec/bitcoin/data_access/satoshi/satoshi_wallet_spec.rb +102 -0
  59. data/spec/bitcoin/domain/address_book_spec.rb +63 -0
  60. data/spec/bitcoin/domain/bitcoin_address_spec.rb +52 -0
  61. data/spec/bitcoin/filesystem/empty_temp_dir_spec.rb +170 -0
  62. data/spec/bitcoin/rspec/argument_matchers/block_evaluating_to_matcher_spec.rb +36 -0
  63. data/spec/spec_helper.rb +29 -1
  64. metadata +221 -18
@@ -1,3 +1,3 @@
1
1
  module Bitcoin
2
- VERSION = "0.1.0"
2
+ VERSION = "0.1.2"
3
3
  end
@@ -0,0 +1,128 @@
1
+ require 'spec_helper'
2
+
3
+ require 'bitcoin/cli'
4
+
5
+ # Dependencies only used in the spec
6
+ require 'bitcoin/console/capturing_stream_bundle'
7
+
8
+ module Bitcoin
9
+ describe CLI do
10
+ let(:stream_bundle) { Console::CapturingStreamBundle.test_bundle }
11
+ let(:cli) { CLI.new(stream_bundle) }
12
+
13
+ describe "help" do
14
+ shared_examples_for "inputs that trigger a full help message" do
15
+ let(:help_command) { mock(Commands::HelpCommand, show_help: nil) }
16
+
17
+ before(:each) do
18
+ Commands::HelpCommand.stub(new: help_command)
19
+ end
20
+
21
+ it "creates a Help Command" do
22
+ Commands::HelpCommand.should_receive(:new).with(stream_bundle)
23
+ run_command
24
+ end
25
+
26
+ it "shows the full help" do
27
+ help_command.should_receive(:show_help)
28
+ run_command
29
+ end
30
+
31
+ it "returns 0", because: "the CLI wrapper exits with this value" do
32
+ run_command.should eq 0
33
+ end
34
+ end
35
+
36
+ describe "<no command>" do
37
+ def run_command
38
+ cli.run([ ])
39
+ end
40
+
41
+ it_behaves_like "inputs that trigger a full help message"
42
+ end
43
+
44
+ describe "help" do
45
+ def run_command
46
+ cli.run(%w[ help ])
47
+ end
48
+
49
+ it_behaves_like "inputs that trigger a full help message"
50
+ end
51
+
52
+ describe "<unknown command>" do
53
+ let(:help_command) { mock(Commands::HelpCommand, show_unknown_command_help: nil) }
54
+
55
+ before(:each) do
56
+ Commands::HelpCommand.stub(new: help_command)
57
+ end
58
+
59
+ def run_command
60
+ cli.run(%w[ thiscommanddoesnotexist ])
61
+ end
62
+
63
+ it "creates a Help Command" do
64
+ Commands::HelpCommand.should_receive(:new).with(stream_bundle)
65
+ run_command
66
+ end
67
+
68
+ it "shows the unknown command help" do
69
+ help_command.should_receive(:show_unknown_command_help).with("thiscommanddoesnotexist")
70
+ run_command
71
+ end
72
+
73
+ it "returns 1", because: "the CLI wrapper exits with this value" do
74
+ run_command.should eq 1
75
+ end
76
+ end
77
+ end
78
+
79
+ describe "satoshi-wallet" do
80
+ let(:wallet_command) { mock(Commands::SatoshiWalletCommand, run: nil) }
81
+ let(:satoshi_wallet_command_environment) { mock(Commands::SatoshiWalletCommandEnvironment) }
82
+
83
+ before(:each) do
84
+ Commands::SatoshiWalletCommand.stub(new: wallet_command)
85
+ Commands::SatoshiWalletCommandEnvironment.stub(new: satoshi_wallet_command_environment)
86
+ end
87
+
88
+ def run_command
89
+ cli.run(%W[ satoshi-wallet subcommand-name wallet.dat foo bar ])
90
+ end
91
+
92
+ it "creates an environment" do
93
+ Commands::SatoshiWalletCommandEnvironment.should_receive(:new).with(stream_bundle)
94
+ run_command
95
+ end
96
+
97
+ it "creates a command" do
98
+ Commands::SatoshiWalletCommand.should_receive(:new).with(stream_bundle, satoshi_wallet_command_environment)
99
+ run_command
100
+ end
101
+
102
+ it "runs the command" do
103
+ wallet_command.should_receive(:run).with(%w[ subcommand-name wallet.dat foo bar ])
104
+ run_command
105
+ end
106
+
107
+ context "the command succeeds" do
108
+ before(:each) do
109
+ wallet_command.stub(run: nil)
110
+ end
111
+
112
+ it "returns 0", because: "the CLI wrapper exits with this value" do
113
+ run_command.should eq 0
114
+ end
115
+ end
116
+
117
+ context "the command fails" do
118
+ before(:each) do
119
+ wallet_command.stub(:run).and_raise(CLI::CommandError.new)
120
+ end
121
+
122
+ it "returns 1", because: "the CLI wrapper exits with this value" do
123
+ run_command.should eq 1
124
+ end
125
+ end
126
+ end
127
+ end
128
+ end
@@ -0,0 +1,53 @@
1
+ require 'spec_helper'
2
+
3
+ require 'bitcoin/commands/help_command'
4
+ require 'bitcoin/console/capturing_stream_bundle'
5
+
6
+ module Bitcoin
7
+ module Commands
8
+ describe HelpCommand do
9
+ let(:stream_bundle) { Console::CapturingStreamBundle.test_bundle }
10
+
11
+ subject { HelpCommand.new(stream_bundle) }
12
+
13
+ describe "#show_help" do
14
+ it "prints the full help" do
15
+ subject.show_help
16
+ # Duplicated from the Cucumber feature to get us going
17
+ stream_bundle.captured_output.should eq(
18
+ -%{
19
+ Usage: rbcoin COMMAND ...
20
+
21
+ Commands:
22
+ help Display help about rbcoin and rbcoin commands
23
+ satoshi-wallet Manipulate the wallet.dat file of the original client
24
+
25
+ Use "rbcoin COMMAND --help" or "rbcoin help COMMAND" for help on a single command
26
+ Use "rbcoin --version" to see the rbcoin version number
27
+ Use "rbcoin --exact-version" to see the all version details (with dependencies)
28
+ } + "\n"
29
+ )
30
+ end
31
+ end
32
+
33
+ describe "#show_unknown_command_help" do
34
+ it "prints an error message to the output", because: "it's currently not this object that detected the error" do
35
+ subject.show_unknown_command_help("thiscommanddoesnotexist")
36
+ # Duplicated from the Cucumber feature
37
+ stream_bundle.captured_output.should eq(
38
+ -%{
39
+ rbcoin failed: No such command "thiscommanddoesnotexist"
40
+ Try: `rbcoin help`
41
+ } + "\n"
42
+ )
43
+ end
44
+
45
+ it "doesn't raise an error", because: "it's currently not this object that detected the error" do
46
+ expect {
47
+ subject.show_unknown_command_help("thiscommanddoesnotexist")
48
+ }.to_not raise_error
49
+ end
50
+ end
51
+ end
52
+ end
53
+ end
@@ -0,0 +1,149 @@
1
+ # -*- encoding: utf-8 -*-
2
+
3
+ require 'spec_helper'
4
+
5
+ require 'bitcoin/commands/satoshi_wallet/add_address_command'
6
+
7
+ # Dependencies required only in the spec
8
+ require 'bitcoin/console/capturing_stream_bundle'
9
+ require 'bitcoin/data_access/satoshi/satoshi_wallet'
10
+
11
+ module Bitcoin
12
+ module Commands
13
+ module SatoshiWallet
14
+ describe AddAddressCommand do
15
+ let(:stream_bundle) { Console::CapturingStreamBundle.test_bundle }
16
+ let(:wallet) { mock(DataAccess::Satoshi::SatoshiWallet, add_address: nil) }
17
+
18
+ let(:subject) { AddAddressCommand.new(stream_bundle) }
19
+
20
+ context "the input args are valid" do
21
+ def run_command
22
+ subject.run(wallet, args: [ "14Z1mazY4HfysZyMaKudFr63EwHqQT2njz", "Bitcoin Monitor" ])
23
+ end
24
+
25
+ it "adds the address" do
26
+ wallet.should_receive(:add_address).with(
27
+ Domain::BitcoinAddress.new("14Z1mazY4HfysZyMaKudFr63EwHqQT2njz"),
28
+ name: "Bitcoin Monitor"
29
+ )
30
+ run_command
31
+ end
32
+
33
+ it "informs the user that the address was added" do
34
+ run_command
35
+ stream_bundle.captured_output.should eq "Address added successfully\n"
36
+ end
37
+ end
38
+
39
+ context "the input args are invalid" do
40
+ context "missing arguments" do
41
+ invalid_arg_sets = [
42
+ %w[ ], # Pressed enter too soon
43
+ %w[ 14Z1mazY4HfysZyMaKudFr63EwHqQT2njz ] # Missing name
44
+ ]
45
+
46
+ invalid_arg_sets.each do |invalid_args|
47
+ it "prints an error message" do
48
+ expect {
49
+ subject.run(wallet, args: invalid_args)
50
+ }.to raise_error
51
+
52
+ stream_bundle.captured_error.should include("You must provide both a Bitcoin address and a name to add to a wallet")
53
+ stream_bundle.captured_error.should =~ /No changes were made to the wallet\n\Z/
54
+ end
55
+
56
+ it "raises an error" do
57
+ expect {
58
+ subject.run(wallet, args: invalid_args)
59
+ }.to raise_error(CLI::CommandError)
60
+ end
61
+ end
62
+ end
63
+
64
+ context "too many args" do
65
+ def run_command
66
+ subject.run(wallet, args: %w[ 14Z1mazY4HfysZyMaKudFr63EwHqQT2njz foo bar baz ])
67
+ end
68
+
69
+ it "prints a helpful error message" do
70
+ expect { run_command }.to raise_error
71
+
72
+ stream_bundle.captured_error.should include(
73
+ -%{
74
+ Received too many arguments
75
+
76
+ To make sure the name is formatted correctly we ask you to use use quotes
77
+ around the name, e.g.: "foo bar baz"
78
+
79
+ No changes were made to the wallet
80
+ }
81
+ )
82
+ stream_bundle.captured_error.should =~ /\n\Z/
83
+ end
84
+
85
+ it "raises an error" do
86
+ expect { run_command }.to raise_error(CLI::CommandError)
87
+ end
88
+ end
89
+
90
+ context "invalid Bitcoin address" do
91
+ def run_command
92
+ subject.run(wallet, args: [ "Invalid Bitcoin address", "Bitcoin Monitor" ])
93
+ end
94
+
95
+ it "prints an error message" do
96
+ expect {
97
+ run_command
98
+ }.to raise_error
99
+
100
+ # Ugh! There's got to be a better way of getting the error message than this,
101
+ # hopefully without having to stub out the creating of a value object...
102
+ expected_error =
103
+ begin
104
+ Domain::BitcoinAddress.new("Invalid Bitcoin address")
105
+ rescue Domain::BitcoinAddress::InvalidBitcoinAddressError => error
106
+ error.message
107
+ end
108
+
109
+ stream_bundle.captured_error.should include(error.message)
110
+ stream_bundle.captured_error.should =~ /The address was not added to the wallet\n\Z/
111
+ end
112
+
113
+ it "raises an error" do
114
+ expect {
115
+ run_command
116
+ }.to raise_error(CLI::CommandError)
117
+ end
118
+ end
119
+
120
+ # This is paranoid, but currently we don't handle string length properly in the Berkeley DB code
121
+ # (This error handling is not publically documented)
122
+ context "excessively long names" do
123
+ string_253_bytes_long = "#{"☃" * 84}."
124
+
125
+ define_method :run_command do
126
+ subject.run(wallet, args: [ "14Z1mazY4HfysZyMaKudFr63EwHqQT2njz", string_253_bytes_long ])
127
+ end
128
+
129
+ it "prints an error message" do
130
+ expect {
131
+ run_command
132
+ }.to raise_error
133
+
134
+ stream_bundle.captured_error.should include(%'The address name "#{string_253_bytes_long}" is too long\n')
135
+ stream_bundle.captured_error.should include("Currently we do not allow address names over 252 bytes long\n")
136
+ stream_bundle.captured_error.should =~ /The address was not added to the wallet\n\Z/
137
+ end
138
+
139
+ it "raises an error" do
140
+ expect {
141
+ run_command
142
+ }.to raise_error(CLI::CommandError)
143
+ end
144
+ end
145
+ end
146
+ end
147
+ end
148
+ end
149
+ end
@@ -0,0 +1,26 @@
1
+ require 'spec_helper'
2
+
3
+ require 'bitcoin/commands/satoshi_wallet/show_addresses_command'
4
+
5
+ # Dependencies required only in the spec
6
+ require 'bitcoin/console/capturing_stream_bundle'
7
+ require 'bitcoin/data_access/satoshi/satoshi_wallet'
8
+
9
+ module Bitcoin
10
+ module Commands
11
+ module SatoshiWallet
12
+ describe ShowAddressesCommand do
13
+ let(:stream_bundle) { Console::CapturingStreamBundle.test_bundle }
14
+ let(:address_book) { mock(Domain::AddressBook, to_s: "addresses") }
15
+ let(:wallet) { mock(DataAccess::Satoshi::SatoshiWallet, addresses: address_book) }
16
+
17
+ let(:subject) { ShowAddressesCommand.new(stream_bundle) }
18
+
19
+ it "prints the addresses to the output stream" do
20
+ subject.run(wallet, wallet_filename: "wallet.dat")
21
+ stream_bundle.captured_output.should eq "# All addresses in wallet.dat\naddresses\n"
22
+ end
23
+ end
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,26 @@
1
+ require 'spec_helper'
2
+
3
+ require 'bitcoin/commands/satoshi_wallet/show_version_command'
4
+
5
+ # Dependencies required only in the spec
6
+ require 'bitcoin/console/capturing_stream_bundle'
7
+ require 'bitcoin/data_access/satoshi/satoshi_version'
8
+ require 'bitcoin/data_access/satoshi/satoshi_wallet'
9
+
10
+ module Bitcoin
11
+ module Commands
12
+ module SatoshiWallet
13
+ describe ShowVersionCommand do
14
+ let(:stream_bundle) { Console::CapturingStreamBundle.test_bundle }
15
+ let(:wallet) { mock(DataAccess::Satoshi::SatoshiWallet, version: DataAccess::Satoshi::SatoshiVersion.new(0, 3, 24)) }
16
+
17
+ let(:subject) { ShowVersionCommand.new(stream_bundle) }
18
+
19
+ it "prints the wallet's version" do
20
+ subject.run(wallet)
21
+ stream_bundle.captured_output.should eq "0.3.24\n"
22
+ end
23
+ end
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,76 @@
1
+ require 'spec_helper'
2
+
3
+ require 'bitcoin/commands/satoshi_wallet_command_environment'
4
+
5
+ # Dependencies required only in the spec
6
+ require 'bitcoin/data_access/satoshi/satoshi_wallet'
7
+ require 'bitcoin/console/capturing_stream_bundle'
8
+
9
+ module Bitcoin
10
+ module Commands
11
+ describe SatoshiWalletCommandEnvironment do
12
+ include FakeFS::SpecHelpers
13
+
14
+ let(:empty_temp_dir) { mock(FileSystem::EmptyTempDir, absolute_path: "/path/to/temp/dir") }
15
+ let(:satoshi_wallet) { mock(DataAccess::Satoshi::SatoshiWallet) }
16
+
17
+ before(:each) do
18
+ FileSystem::EmptyTempDir.stub(:open).and_yield(empty_temp_dir)
19
+ DataAccess::Satoshi::SatoshiWallet.stub(:open).and_yield(satoshi_wallet)
20
+ end
21
+
22
+ let(:command) { mock(Commands::SatoshiWallet, run: nil) }
23
+ let(:stream_bundle) { Console::CapturingStreamBundle.test_bundle }
24
+
25
+ subject { SatoshiWalletCommandEnvironment.new(stream_bundle) }
26
+
27
+ context "the wallet file exists" do
28
+ before(:each) { File.open("wallet.dat", "w") { |file| file << "unused wallet data" } }
29
+
30
+ it "create temp dir" do
31
+ FileSystem::EmptyTempDir.should_receive(:open).with(with_name: "bitcoinrb_db_tmp", parallel_to: PROJECT_ROOT + "/wallet.dat")
32
+ subject.run_command(command, %w[ wallet.dat foo bar ])
33
+ end
34
+
35
+ it "opens the wallet" do
36
+ DataAccess::Satoshi::SatoshiWallet.should_receive(:open).with(
37
+ wallet_filename: File.join(PROJECT_ROOT, "/wallet.dat"), # because this is the directory the specs run in
38
+ db_dirname: "/path/to/temp/dir"
39
+ )
40
+ subject.run_command(command, %w[ wallet.dat foo bar ])
41
+ end
42
+
43
+ it "runs the command" do
44
+ command.should_receive(:run).with(satoshi_wallet, wallet_filename: "wallet.dat", args: %w[ foo bar ])
45
+ subject.run_command(command, %w[ wallet.dat foo bar ])
46
+ end
47
+ end
48
+
49
+ context "the wallet file does not exist" do
50
+ it "prints an error" do
51
+ expect {
52
+ subject.run_command(command, %w[ missing_wallet.dat foo bar ])
53
+ }.to raise_error
54
+
55
+ stream_bundle.captured_error.should eq "Couldn't find wallet file: missing_wallet.dat\n"
56
+ end
57
+
58
+ it "raises a CLI::CommandError" do
59
+ expect {
60
+ subject.run_command(command, %w[ missing_wallet.dat foo bar ])
61
+ }.to raise_error(CLI::CommandError)
62
+ end
63
+
64
+ it "does not attempt the rest of the code" do
65
+ # Although this does involve a lot of knowledge about the code, as
66
+ # it's not immediately obvious why this proves nothing else is run
67
+ FileSystem::EmptyTempDir.should_not_receive(:open)
68
+
69
+ expect {
70
+ subject.run_command(command, %w[ missing_wallet.dat foo bar ])
71
+ }.to raise_error
72
+ end
73
+ end
74
+ end
75
+ end
76
+ end