Permalink
Browse files

Did a little clean-up based on a little walk-through of the

command error handling code.

	Refactored ICouchCommand's error handling to be a
	method instead of an Action.  Not needed since
	CouchProxy has an instance of the CouchCommand
	being executed when an exception is thrown.

	Added the JsonMember attributes needed by
	BulkDocsMessage and BulkDocsResult to be serialized
	or deserialized correctly.

	Added better error handling when connecting to a
	CouchDB server.

	Added better error handling for creating and
	deleting databases.
  • Loading branch information...
1 parent f8a9814 commit d972550b0ab7ce23075a243f24940e88ad31453a @dragan dragan committed Aug 29, 2010
Showing with 295 additions and 16 deletions.
  1. +10 −0 src/SineSignal.Ottoman.AcceptanceSpecs/Developer_administers_databases_on_server.feature
  2. +34 −0 src/SineSignal.Ottoman.AcceptanceSpecs/Developer_administers_databases_on_server.feature.cs
  3. +5 −0 src/SineSignal.Ottoman.AcceptanceSpecs/Developer_connects_to_server.feature
  4. +17 −0 src/SineSignal.Ottoman.AcceptanceSpecs/Developer_connects_to_server.feature.cs
  5. +43 −2 src/SineSignal.Ottoman.AcceptanceSpecs/StepDefinitions/DeveloperAdministersDatabasesOnServer.cs
  6. +22 −1 src/SineSignal.Ottoman.AcceptanceSpecs/StepDefinitions/DeveloperConnectsToServerSteps.cs
  7. +47 −0 src/SineSignal.Ottoman.Specs/CouchProxySpecs.cs
  8. +18 −1 src/SineSignal.Ottoman/Commands/BulkDocsCommand.cs
  9. +5 −1 src/SineSignal.Ottoman/Commands/ConnectToServerCommand.cs
  10. +10 −2 src/SineSignal.Ottoman/Commands/CreateDatabaseCommand.cs
  11. +10 −2 src/SineSignal.Ottoman/Commands/DeleteDatabaseCommand.cs
  12. +1 −3 src/SineSignal.Ottoman/Commands/GetDatabaseCommand.cs
  13. +1 −1 src/SineSignal.Ottoman/Commands/ICouchCommand.cs
  14. +11 −2 src/SineSignal.Ottoman/CouchProxy.cs
  15. +16 −0 src/SineSignal.Ottoman/Exceptions/CannotConnectToServerException.cs
  16. +16 −0 src/SineSignal.Ottoman/Exceptions/CannotCreateDatabaseException.cs
  17. +16 −0 src/SineSignal.Ottoman/Exceptions/CannotDeleteDatabaseException.cs
  18. +8 −1 src/SineSignal.Ottoman/Http/HttpClient.cs
  19. +2 −0 src/SineSignal.Ottoman/Http/IRestClient.cs
  20. +3 −0 src/SineSignal.Ottoman/SineSignal.Ottoman.csproj
View
10 src/SineSignal.Ottoman.AcceptanceSpecs/Developer_administers_databases_on_server.feature
@@ -14,3 +14,13 @@ Scenario: Create database
Scenario: Delete database
When I call DeleteDatabase on CouchClient
Then the result should be the database was deleted on the server
+
+Scenario: Cannot create database
+ Given I have an invalid name for a database
+ When I call CreateDatabase on CouchClient
+ Then the result should be a CannotCreateDatabaseException
+
+Scenario: Cannot delete database
+ Given I have a name for a database that doesn't exist on the server
+ When I call DeleteDatabase on CouchClient
+ Then the result should be a CannotDeleteDatabaseException
View
34 src/SineSignal.Ottoman.AcceptanceSpecs/Developer_administers_databases_on_server.feature.cs
@@ -91,6 +91,40 @@ public virtual void DeleteDatabase()
#line hidden
testRunner.CollectScenarioErrors();
}
+
+ [NUnit.Framework.TestAttribute()]
+ [NUnit.Framework.DescriptionAttribute("Cannot create database")]
+ public virtual void CannotCreateDatabase()
+ {
+ TechTalk.SpecFlow.ScenarioInfo scenarioInfo = new TechTalk.SpecFlow.ScenarioInfo("Cannot create database", ((string[])(null)));
+#line 18
+this.ScenarioSetup(scenarioInfo);
+#line 19
+testRunner.Given("I have an invalid name for a database");
+#line 20
+testRunner.When("I call CreateDatabase on CouchClient");
+#line 21
+testRunner.Then("the result should be a CannotCreateDatabaseException");
+#line hidden
+ testRunner.CollectScenarioErrors();
+ }
+
+ [NUnit.Framework.TestAttribute()]
+ [NUnit.Framework.DescriptionAttribute("Cannot delete database")]
+ public virtual void CannotDeleteDatabase()
+ {
+ TechTalk.SpecFlow.ScenarioInfo scenarioInfo = new TechTalk.SpecFlow.ScenarioInfo("Cannot delete database", ((string[])(null)));
+#line 23
+this.ScenarioSetup(scenarioInfo);
+#line 24
+testRunner.Given("I have a name for a database that doesn't exist on the server");
+#line 25
+testRunner.When("I call DeleteDatabase on CouchClient");
+#line 26
+testRunner.Then("the result should be a CannotDeleteDatabaseException");
+#line hidden
+ testRunner.CollectScenarioErrors();
+ }
}
}
#endregion
View
5 src/SineSignal.Ottoman.AcceptanceSpecs/Developer_connects_to_server.feature
@@ -8,3 +8,8 @@ Scenario: Connect to server
When I call ConnectTo on CouchClient
Then the result should be an instance of CouchClient
And ServerVersion should not be null or empty
+
+Scenario: Cannot connect to server
+ Given I do not have a CouchDB instance running at http://127.0.0.1:5985
+ When I call ConnectTo on CouchClient
+ Then the result should be a CannotConnectToServerException
View
17 src/SineSignal.Ottoman.AcceptanceSpecs/Developer_connects_to_server.feature.cs
@@ -69,6 +69,23 @@ public virtual void ConnectToServer()
#line hidden
testRunner.CollectScenarioErrors();
}
+
+ [NUnit.Framework.TestAttribute()]
+ [NUnit.Framework.DescriptionAttribute("Cannot connect to server")]
+ public virtual void CannotConnectToServer()
+ {
+ TechTalk.SpecFlow.ScenarioInfo scenarioInfo = new TechTalk.SpecFlow.ScenarioInfo("Cannot connect to server", ((string[])(null)));
+#line 12
+this.ScenarioSetup(scenarioInfo);
+#line 13
+testRunner.Given("I do not have a CouchDB instance running at http://127.0.0.1:5985");
+#line 14
+testRunner.When("I call ConnectTo on CouchClient");
+#line 15
+testRunner.Then("the result should be a CannotConnectToServerException");
+#line hidden
+ testRunner.CollectScenarioErrors();
+ }
}
}
#endregion
View
45 ...neSignal.Ottoman.AcceptanceSpecs/StepDefinitions/DeveloperAdministersDatabasesOnServer.cs
@@ -14,6 +14,8 @@ public class DeveloperAdministersDatabasesOnServer
{
private ICouchClient couchClient;
private string databaseName;
+ private CannotCreateDatabaseException createDatabaseException;
+ private CannotDeleteDatabaseException deleteDatabaseException;
[Given("I have an instance of CouchClient")]
public void GivenIHaveAnInstanceOfCouchClient()
@@ -27,10 +29,30 @@ public void GivenIHaveANameForADatabase()
databaseName = "ottoman-test-database";
}
+ [Given("I have an invalid name for a database")]
+ public void GivenIHaveAnInvalidNameForADatabase()
+ {
+ // CouchDB doesn't accept database names with upper-case characters
+ databaseName = "OttomanTestDatabase";
+ }
+
+ [Given("I have a name for a database that doesn't exist on the server")]
+ public void GivenIHaveANameForADatabaseThatDoesntExistOnTheServer()
+ {
+ databaseName = "x";
+ }
+
[When("I call CreateDatabase on CouchClient")]
public void WhenICallCreateDatabaseOnCouchClient()
{
- couchClient.CreateDatabase(databaseName);
+ try
+ {
+ couchClient.CreateDatabase(databaseName);
+ }
+ catch (CannotCreateDatabaseException e)
+ {
+ createDatabaseException = e;
+ }
}
[Then("the result should be the database was created on the server")]
@@ -43,7 +65,14 @@ public void ThenTheResultShouldBeTheDatabaseWasCreatedOnTheServer()
[When("I call DeleteDatabase on CouchClient")]
public void WhenICallDeleteDatabaseOnCouchClient()
{
- couchClient.DeleteDatabase(databaseName);
+ try
+ {
+ couchClient.DeleteDatabase(databaseName);
+ }
+ catch (CannotDeleteDatabaseException e)
+ {
+ deleteDatabaseException = e;
+ }
}
[Then("the result should be the database was deleted on the server")]
@@ -58,5 +87,17 @@ public void ThenTheResultShouldBeTheDatabaseWasDeletedOnTheServer()
Assert.That(e, Is.TypeOf(typeof(CannotGetDatabaseException)));
}
}
+
+ [Then("the result should be a CannotCreateDatabaseException")]
+ public void ThenTheResultShouldBeACannotCreateDatabaseException()
+ {
+ Assert.That(createDatabaseException, Is.TypeOf(typeof(CannotCreateDatabaseException)));
+ }
+
+ [Then("the result should be a CannotDeleteDatabaseException")]
+ public void ThenTheResultShouldBeACannotDeleteDatabaseException()
+ {
+ Assert.That(deleteDatabaseException, Is.TypeOf(typeof(CannotDeleteDatabaseException)));
+ }
}
}
View
23 src/SineSignal.Ottoman.AcceptanceSpecs/StepDefinitions/DeveloperConnectsToServerSteps.cs
@@ -5,6 +5,7 @@
using TechTalk.SpecFlow;
using SineSignal.Ottoman;
+using SineSignal.Ottoman.Exceptions;
namespace SineSignal.Ottoman.AcceptanceSpecs
{
@@ -13,6 +14,7 @@ public class DeveloperConnectsToServerSteps
{
private string address;
private ICouchClient couchClient;
+ private CannotConnectToServerException exception;
[Given("I have a CouchDB instance running at http://127.0.0.1:5984")]
public void GivenIHaveACouchDBInstanceRunningAtHttp127_0_0_15984()
@@ -23,7 +25,14 @@ public void GivenIHaveACouchDBInstanceRunningAtHttp127_0_0_15984()
[When("I call ConnectTo on CouchClient")]
public void WhenICallConnectToOnCouchClient()
{
- couchClient = CouchClient.ConnectTo(address);
+ try
+ {
+ couchClient = CouchClient.ConnectTo(address);
+ }
+ catch (CannotConnectToServerException e)
+ {
+ exception = e;
+ }
}
[Then("the result should be an instance of CouchClient")]
@@ -37,5 +46,17 @@ public void ThenServerVersionShouldNotBeNullOrEmpty()
{
Assert.That(String.IsNullOrEmpty(couchClient.ServerVersion), Is.False);
}
+
+ [Given("I do not have a CouchDB instance running at http://127.0.0.1:5985")]
+ public void GivenIDoNotHaveACouchDBInstanceRunningAtHttp127_0_0_15985()
+ {
+ address = "http://127.0.0.1:5985";
+ }
+
+ [Then("the result should be a CannotConnectToServerException")]
+ public void ThenTheResultShouldBeACannotConnectToServerException()
+ {
+ Assert.That(exception, Is.TypeOf(typeof(CannotConnectToServerException)));
+ }
}
}
View
47 src/SineSignal.Ottoman.Specs/CouchProxySpecs.cs
@@ -130,6 +130,53 @@ public void Should_return_deserialized_object()
Assert.That(resultStub.Status, Is.EqualTo("completed"));
}
}
+
+ [Ignore("Until we can figure out how to tell NSubstitute to throw an exception when Process is called")]
+ public class When_executing_a_command_that_causes_an_unexpected_response_by_rest_client : ConcernFor<CouchProxy>
+ {
+ private IRestClient restClient;
+ private ICouchCommand couchCommand;
+
+ protected override void Given()
+ {
+ restClient = Fake<IRestClient>();
+
+ couchCommand = Fake<ICouchCommand>();
+ couchCommand.Route.Returns("some/path");
+ couchCommand.Operation.Returns(HttpMethod.Get);
+ couchCommand.SuccessStatusCode.Returns(HttpStatusCode.OK);
+
+ // TODO: How to tell restClient to throw exception when Process gets called.
+ }
+
+ public override CouchProxy CreateSystemUnderTest()
+ {
+ return new CouchProxy(restClient);
+ }
+
+ protected override void When()
+ {
+ Sut.Execute<ResultStub>(couchCommand);
+ }
+
+ [Test]
+ public void Should_call_process_on_rest_client_with_generated_rest_request()
+ {
+ restClient.Received().Process<ResultStub>(Arg.Is<RestRequest>(r => {
+ return r.Path == "some/path" &&
+ r.Method == HttpMethod.Get &&
+ r.Payload == null;
+ }), Arg.Is<HttpStatusCode>(h => {
+ return h == HttpStatusCode.OK;
+ }));
+ }
+
+ [Test]
+ public void Should_call_error_handler_on_couch_command()
+ {
+ // TODO: Make sure HandleError on CouchCommand is called
+ }
+ }
}
public class ResultStub
View
19 src/SineSignal.Ottoman/Commands/BulkDocsCommand.cs
@@ -5,6 +5,7 @@
using SineSignal.Ottoman.Exceptions;
using SineSignal.Ottoman.Http;
+using SineSignal.Ottoman.Serialization;
namespace SineSignal.Ottoman.Commands
{
@@ -22,7 +23,6 @@ public object Message
}
public HttpStatusCode SuccessStatusCode { get; private set; }
- public Action<CommandErrorResult, UnexpectedHttpResponseException> OnErrorHandler { get { throw new NotImplementedException(); } }
public BulkDocsCommand(string databaseName, BulkDocsMessage message)
{
@@ -31,12 +31,22 @@ public BulkDocsCommand(string databaseName, BulkDocsMessage message)
_message = message;
SuccessStatusCode = HttpStatusCode.Created;
}
+
+ public void HandleError(string serverAddress, CommandErrorResult errorResult, UnexpectedHttpResponseException innerException)
+ {
+ throw new NotImplementedException();
+ }
}
public class BulkDocsMessage
{
+ [JsonMember("non_atomic")]
public bool NonAtomic { get; private set; }
+
+ [JsonMember("all_or_nothing")]
public bool AllOrNothing { get; private set; }
+
+ [JsonMember("docs")]
public CouchDocument[] Docs { get; private set; }
public BulkDocsMessage(IEnumerable<CouchDocument> docs) : this(false, false, docs)
@@ -53,9 +63,16 @@ public BulkDocsMessage(bool nonAtomic, bool allOrNothing, IEnumerable<CouchDocum
public class BulkDocsResult
{
+ [JsonMember("id")]
public string Id { get; set; }
+
+ [JsonMember("rev")]
public string Rev { get; set; }
+
+ [JsonMember("error")]
public string Error { get; set; }
+
+ [JsonMember("reason")]
public string Reason { get; set; }
}
}
View
6 src/SineSignal.Ottoman/Commands/ConnectToServerCommand.cs
@@ -13,7 +13,6 @@ public class ConnectToServerCommand : ICouchCommand
public string Operation { get; private set; }
public object Message { get; private set; }
public HttpStatusCode SuccessStatusCode { get; private set; }
- public Action<CommandErrorResult, UnexpectedHttpResponseException> OnErrorHandler { get { throw new NotImplementedException(); } }
public ConnectToServerCommand()
{
@@ -22,6 +21,11 @@ public ConnectToServerCommand()
Message = null;
SuccessStatusCode = HttpStatusCode.OK;
}
+
+ public void HandleError(string serverAddress, CommandErrorResult errorResult, UnexpectedHttpResponseException innerException)
+ {
+ throw new CannotConnectToServerException(serverAddress, errorResult, innerException);
+ }
}
public class ConnectToServerResult
View
12 src/SineSignal.Ottoman/Commands/CreateDatabaseCommand.cs
@@ -12,14 +12,22 @@ public class CreateDatabaseCommand : ICouchCommand
public string Operation { get; private set; }
public object Message { get; private set; }
public HttpStatusCode SuccessStatusCode { get; private set; }
- public Action<CommandErrorResult, UnexpectedHttpResponseException> OnErrorHandler { get { throw new NotImplementedException(); } }
+
+ private string DatabaseName { get; set; }
public CreateDatabaseCommand(string databaseName)
{
- Route = databaseName;
+ DatabaseName = databaseName;
+
+ Route = DatabaseName;
Operation = HttpMethod.Put;
Message = null;
SuccessStatusCode = HttpStatusCode.Created;
}
+
+ public void HandleError(string serverAddress, CommandErrorResult errorResult, UnexpectedHttpResponseException innerException)
+ {
+ throw new CannotCreateDatabaseException(DatabaseName, errorResult, innerException);
+ }
}
}
View
12 src/SineSignal.Ottoman/Commands/DeleteDatabaseCommand.cs
@@ -12,14 +12,22 @@ public class DeleteDatabaseCommand : ICouchCommand
public string Operation { get; private set; }
public object Message { get; private set; }
public HttpStatusCode SuccessStatusCode { get; private set; }
- public Action<CommandErrorResult, UnexpectedHttpResponseException> OnErrorHandler { get { throw new NotImplementedException(); } }
+
+ private string DatabaseName { get; set; }
public DeleteDatabaseCommand(string databaseName)
{
- Route = databaseName;
+ DatabaseName = databaseName;
+
+ Route = DatabaseName;
Operation = HttpMethod.Delete;
Message = null;
SuccessStatusCode = HttpStatusCode.OK;
}
+
+ public void HandleError(string serverAddress, CommandErrorResult errorResult, UnexpectedHttpResponseException innerException)
+ {
+ throw new CannotDeleteDatabaseException(DatabaseName, errorResult, innerException);
+ }
}
}
View
4 src/SineSignal.Ottoman/Commands/GetDatabaseCommand.cs
@@ -13,7 +13,6 @@ public class GetDatabaseCommand : ICouchCommand
public string Operation { get; private set; }
public object Message { get; private set; }
public HttpStatusCode SuccessStatusCode { get; private set; }
- public Action<CommandErrorResult, UnexpectedHttpResponseException> OnErrorHandler { get; private set; }
private string DatabaseName { get; set; }
@@ -25,10 +24,9 @@ public GetDatabaseCommand(string databaseName)
Operation = HttpMethod.Get;
Message = null;
SuccessStatusCode = HttpStatusCode.OK;
- OnErrorHandler = OnError;
}
- private void OnError(CommandErrorResult errorResult, UnexpectedHttpResponseException innerException)
+ public void HandleError(string serverAddress, CommandErrorResult errorResult, UnexpectedHttpResponseException innerException)
{
throw new CannotGetDatabaseException(DatabaseName, errorResult, innerException);
}
View
2 src/SineSignal.Ottoman/Commands/ICouchCommand.cs
@@ -13,7 +13,7 @@ public interface ICouchCommand
string Operation { get; }
object Message { get; }
HttpStatusCode SuccessStatusCode { get; }
- Action<CommandErrorResult, UnexpectedHttpResponseException> OnErrorHandler { get; }
+ void HandleError(string serverAddress, CommandErrorResult errorResult, UnexpectedHttpResponseException innerException);
}
public class CommandDefaultResult
View
13 src/SineSignal.Ottoman/CouchProxy.cs
@@ -35,8 +35,17 @@ public TResult Execute<TResult>(ICouchCommand couchCommand)
}
catch (UnexpectedHttpResponseException e)
{
- var errorResult = Serializer.Deserialize<CommandErrorResult>(e.RawResponse.Content);
- couchCommand.OnErrorHandler(errorResult, e);
+ CommandErrorResult errorResult;
+ if (!String.IsNullOrEmpty(e.RawResponse.Content))
+ {
+ errorResult = Serializer.Deserialize<CommandErrorResult>(e.RawResponse.Content);
+ }
+ else
+ {
+ errorResult = new CommandErrorResult { Error = "Unexpected Exception", Reason = e.RawResponse.Error.Message };
+ }
+
+ couchCommand.HandleError(RestClient.BaseUri.ToString(), errorResult, e);
}
return restResponse.ContentDeserialized;
View
16 src/SineSignal.Ottoman/Exceptions/CannotConnectToServerException.cs
@@ -0,0 +1,16 @@
+using System;
+
+using SineSignal.Ottoman.Commands;
+
+namespace SineSignal.Ottoman.Exceptions
+{
+ public class CannotConnectToServerException : CouchException
+ {
+ private const string ExceptionMessageFormat = "Unable to connect to '{0}'";
+
+ public CannotConnectToServerException(string address, CommandErrorResult errorResult, UnexpectedHttpResponseException innerException) :
+ base(String.Format(ExceptionMessageFormat, address), errorResult, innerException)
+ {
+ }
+ }
+}
View
16 src/SineSignal.Ottoman/Exceptions/CannotCreateDatabaseException.cs
@@ -0,0 +1,16 @@
+using System;
+
+using SineSignal.Ottoman.Commands;
+
+namespace SineSignal.Ottoman.Exceptions
+{
+ public class CannotCreateDatabaseException : CouchException
+ {
+ private const string ExceptionMessageFormat = "Failed to create database '{0}'";
+
+ public CannotCreateDatabaseException(string databaseName, CommandErrorResult errorResult, UnexpectedHttpResponseException innerException)
+ : base(String.Format(ExceptionMessageFormat, databaseName), errorResult, innerException)
+ {
+ }
+ }
+}
View
16 src/SineSignal.Ottoman/Exceptions/CannotDeleteDatabaseException.cs
@@ -0,0 +1,16 @@
+using System;
+
+using SineSignal.Ottoman.Commands;
+
+namespace SineSignal.Ottoman.Exceptions
+{
+ public class CannotDeleteDatabaseException : CouchException
+ {
+ private const string ExceptionMessageFormat = "Failed to delete database '{0}'";
+
+ public CannotDeleteDatabaseException(string databaseName, CommandErrorResult errorResult, UnexpectedHttpResponseException innerException)
+ : base(String.Format(ExceptionMessageFormat, databaseName), errorResult, innerException)
+ {
+ }
+ }
+}
View
9 src/SineSignal.Ottoman/Http/HttpClient.cs
@@ -72,7 +72,14 @@ private static HttpWebResponse GenerateHttpWebResponse(HttpWebRequest httpWebReq
}
catch (WebException webException)
{
- httpWebResponse = webException.Response as HttpWebResponse;
+ if (webException.InnerException is System.Net.Sockets.SocketException)
+ {
+ throw;
+ }
+ else
+ {
+ httpWebResponse = webException.Response as HttpWebResponse;
+ }
}
return httpWebResponse;
View
2 src/SineSignal.Ottoman/Http/IRestClient.cs
@@ -1,9 +1,11 @@
+using System;
using System.Net;
namespace SineSignal.Ottoman.Http
{
public interface IRestClient
{
+ Uri BaseUri { get; }
RestResponse<T> Process<T>(RestRequest restRequest, HttpStatusCode successStatusCode);
}
}
View
3 src/SineSignal.Ottoman/SineSignal.Ottoman.csproj
@@ -73,6 +73,9 @@
<Compile Include="Generators\IIdentityGenerator.cs" />
<Compile Include="Generators\GuidIdentityGenerator.cs" />
<Compile Include="CouchDocumentConvention.cs" />
+ <Compile Include="Exceptions\CannotConnectToServerException.cs" />
+ <Compile Include="Exceptions\CannotCreateDatabaseException.cs" />
+ <Compile Include="Exceptions\CannotDeleteDatabaseException.cs" />
</ItemGroup>
<ItemGroup>
<Reference Include="System" />

0 comments on commit d972550

Please sign in to comment.