Browse files

Merge branch 'refactoring-command-error-handling' into development

  • Loading branch information...
2 parents f8a9814 + 0836e55 commit 1a461945b1b8190ca1029d173bafabfce5b68011 @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 ...itions/{DeveloperAdministersDatabasesOnServer.cs → DeveloperAdministersDatabasesOnServerSteps.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 .../DeveloperAdministersDatabasesOnServer.cs → ...loperAdministersDatabasesOnServerSteps.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 1a46194

Please sign in to comment.