Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Browse files

Ottoman can now administer databases on a CouchDB server. We support …

…CreateDatabase, GetDatabase, and DeleteDatabase currently. Also added error handling and started mapping readable exceptions to CouchDB error responses.
  • Loading branch information...
commit 578535bf922f2f2bafbe52f95f811e5006786f68 1 parent cb28cc4
@dragan dragan authored
Showing with 853 additions and 67 deletions.
  1. +16 −0 src/SineSignal.Ottoman.AcceptanceSpecs/Developer_administers_databases_on_server.feature
  2. +96 −0 src/SineSignal.Ottoman.AcceptanceSpecs/Developer_administers_databases_on_server.feature.cs
  3. +8 −0 src/SineSignal.Ottoman.AcceptanceSpecs/SineSignal.Ottoman.AcceptanceSpecs.csproj
  4. +62 −0 src/SineSignal.Ottoman.AcceptanceSpecs/StepDefinitions/DeveloperAdministersDatabasesOnServer.cs
  5. +1 −1  src/SineSignal.Ottoman.AcceptanceSpecs/StepDefinitions/DeveloperConnectsToServerSteps.cs
  6. +66 −4 src/SineSignal.Ottoman.Specs/CouchProxySpecs.cs
  7. +300 −55 src/SineSignal.Ottoman.Specs/Http/RestClientSpecs.cs
  8. +6 −0 src/SineSignal.Ottoman/Commands/BulkDocsCommand.cs
  9. +5 −0 src/SineSignal.Ottoman/Commands/ConnectToServerCommand.cs
  10. +25 −0 src/SineSignal.Ottoman/Commands/CreateDatabaseCommand.cs
  11. +25 −0 src/SineSignal.Ottoman/Commands/DeleteDatabaseCommand.cs
  12. +66 −0 src/SineSignal.Ottoman/Commands/GetDatabaseCommand.cs
  13. +22 −0 src/SineSignal.Ottoman/Commands/ICouchCommand.cs
  14. +23 −3 src/SineSignal.Ottoman/CouchClient.cs
  15. +27 −0 src/SineSignal.Ottoman/CouchDatabase.cs
  16. +16 −1 src/SineSignal.Ottoman/CouchProxy.cs
  17. +17 −0 src/SineSignal.Ottoman/Exceptions/CannotGetDatabaseException.cs
  18. +18 −0 src/SineSignal.Ottoman/Exceptions/CouchException.cs
  19. +20 −0 src/SineSignal.Ottoman/Exceptions/UnexpectedHttpResponseException.cs
  20. +3 −1 src/SineSignal.Ottoman/Http/IRestClient.cs
  21. +11 −2 src/SineSignal.Ottoman/Http/RestClient.cs
  22. +12 −0 src/SineSignal.Ottoman/ICouchClient.cs
  23. +8 −0 src/SineSignal.Ottoman/SineSignal.Ottoman.csproj
View
16 src/SineSignal.Ottoman.AcceptanceSpecs/Developer_administers_databases_on_server.feature
@@ -0,0 +1,16 @@
+Feature: Developer administers databases on server
+ In order to create and delete databases
+ As a developer
+ I want to be able to administer databases on the server
+
+Background:
+ Given I have an instance of CouchClient
+ And I have a name for a database
+
+Scenario: Create database
+ When I call CreateDatabase on CouchClient
+ Then the result should be the database was created on the server
+
+Scenario: Delete database
+ When I call DeleteDatabase on CouchClient
+ Then the result should be the database was deleted on the server
View
96 src/SineSignal.Ottoman.AcceptanceSpecs/Developer_administers_databases_on_server.feature.cs
@@ -0,0 +1,96 @@
+// ------------------------------------------------------------------------------
+// <auto-generated>
+// This code was generated by SpecFlow (http://www.specflow.org/).
+// SpecFlow Version:1.3.5.2
+// Runtime Version:2.0.50727.1433
+//
+// Changes to this file may cause incorrect behavior and will be lost if
+// the code is regenerated.
+// </auto-generated>
+// ------------------------------------------------------------------------------
+#region Designer generated code
+namespace SineSignal.Ottoman.AcceptanceSpecs
+{
+ using TechTalk.SpecFlow;
+
+
+ [System.CodeDom.Compiler.GeneratedCodeAttribute("TechTalk.SpecFlow", "1.3.5.2")]
+ [System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
+ [NUnit.Framework.TestFixtureAttribute()]
+ [NUnit.Framework.DescriptionAttribute("Developer administers databases on server")]
+ public partial class DeveloperAdministersDatabasesOnServerFeature
+ {
+
+ private static TechTalk.SpecFlow.ITestRunner testRunner;
+
+ #line 1 "Developer_administers_databases_on_server.feature"
+ #line hidden
+ [NUnit.Framework.TestFixtureSetUpAttribute()]
+ public virtual void FeatureSetup()
+ {
+ testRunner = TechTalk.SpecFlow.TestRunnerManager.GetTestRunner();
+ TechTalk.SpecFlow.FeatureInfo featureInfo = new TechTalk.SpecFlow.FeatureInfo(new System.Globalization.CultureInfo("en-US"), "Developer administers databases on server", "In order to create and delete databases\nAs a developer\nI want to be able to administer databases on the server", ((string[])(null)));
+ testRunner.OnFeatureStart(featureInfo);
+ }
+
+ [NUnit.Framework.TestFixtureTearDownAttribute()]
+ public virtual void FeatureTearDown()
+ {
+ testRunner.OnFeatureEnd();
+ testRunner = null;
+ }
+
+ public virtual void ScenarioSetup(TechTalk.SpecFlow.ScenarioInfo scenarioInfo)
+ {
+ testRunner.OnScenarioStart(scenarioInfo);
+ this.FeatureBackground();
+ }
+
+ [NUnit.Framework.TearDownAttribute()]
+ public virtual void ScenarioTearDown()
+ {
+ testRunner.OnScenarioEnd();
+ }
+
+ public virtual void FeatureBackground()
+ {
+#line 6
+#line 7
+testRunner.Given("I have an instance of CouchClient");
+#line 8
+testRunner.And("I have a name for a database");
+#line hidden
+ }
+
+ [NUnit.Framework.TestAttribute()]
+ [NUnit.Framework.DescriptionAttribute("Create database")]
+ public virtual void CreateDatabase()
+ {
+ TechTalk.SpecFlow.ScenarioInfo scenarioInfo = new TechTalk.SpecFlow.ScenarioInfo("Create database", ((string[])(null)));
+#line 10
+this.ScenarioSetup(scenarioInfo);
+#line 11
+testRunner.When("I call CreateDatabase on CouchClient");
+#line 12
+testRunner.Then("the result should be the database was created on the server");
+#line hidden
+ testRunner.CollectScenarioErrors();
+ }
+
+ [NUnit.Framework.TestAttribute()]
+ [NUnit.Framework.DescriptionAttribute("Delete database")]
+ public virtual void DeleteDatabase()
+ {
+ TechTalk.SpecFlow.ScenarioInfo scenarioInfo = new TechTalk.SpecFlow.ScenarioInfo("Delete database", ((string[])(null)));
+#line 14
+this.ScenarioSetup(scenarioInfo);
+#line 15
+testRunner.When("I call DeleteDatabase on CouchClient");
+#line 16
+testRunner.Then("the result should be the database was deleted on the server");
+#line hidden
+ testRunner.CollectScenarioErrors();
+ }
+ }
+}
+#endregion
View
8 src/SineSignal.Ottoman.AcceptanceSpecs/SineSignal.Ottoman.AcceptanceSpecs.csproj
@@ -47,6 +47,10 @@
<DependentUpon>Developer_connects_to_server.feature</DependentUpon>
</Compile>
<Compile Include="StepDefinitions\DeveloperConnectsToServerSteps.cs" />
+ <Compile Include="Developer_administers_databases_on_server.feature.cs">
+ <DependentUpon>Developer_administers_databases_on_server.feature</DependentUpon>
+ </Compile>
+ <Compile Include="StepDefinitions\DeveloperAdministersDatabasesOnServer.cs" />
</ItemGroup>
<Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" />
<ItemGroup>
@@ -60,6 +64,10 @@
<Generator>SpecFlowSingleFileGenerator</Generator>
<LastGenOutput>Developer_connects_to_server.feature.cs</LastGenOutput>
</None>
+ <None Include="Developer_administers_databases_on_server.feature">
+ <Generator>SpecFlowSingleFileGenerator</Generator>
+ <LastGenOutput>Developer_administers_databases_on_server.feature.cs</LastGenOutput>
+ </None>
</ItemGroup>
<ItemGroup>
<Folder Include="StepDefinitions\" />
View
62 ...neSignal.Ottoman.AcceptanceSpecs/StepDefinitions/DeveloperAdministersDatabasesOnServer.cs
@@ -0,0 +1,62 @@
+using System;
+
+using NUnit.Framework;
+using NUnit.Framework.SyntaxHelpers;
+using TechTalk.SpecFlow;
+
+using SineSignal.Ottoman;
+using SineSignal.Ottoman.Exceptions;
+
+namespace SineSignal.Ottoman.AcceptanceSpecs
+{
+ [Binding]
+ public class DeveloperAdministersDatabasesOnServer
+ {
+ private ICouchClient couchClient;
+ private string databaseName;
+
+ [Given("I have an instance of CouchClient")]
+ public void GivenIHaveAnInstanceOfCouchClient()
+ {
+ couchClient = CouchClient.ConnectTo("http://127.0.0.1:5984");
+ }
+
+ [Given("I have a name for a database")]
+ public void GivenIHaveANameForADatabase()
+ {
+ databaseName = "ottoman-test-database";
+ }
+
+ [When("I call CreateDatabase on CouchClient")]
+ public void WhenICallCreateDatabaseOnCouchClient()
+ {
+ couchClient.CreateDatabase(databaseName);
+ }
+
+ [Then("the result should be the database was created on the server")]
+ public void ThenTheResultShouldBeTheDatabaseWasCreatedOnTheServer()
+ {
+ ICouchDatabase database = couchClient.GetDatabase(databaseName);
+ Assert.That(databaseName, Is.EqualTo(database.Name));
+ }
+
+ [When("I call DeleteDatabase on CouchClient")]
+ public void WhenICallDeleteDatabaseOnCouchClient()
+ {
+ couchClient.DeleteDatabase(databaseName);
+ }
+
+ [Then("the result should be the database was deleted on the server")]
+ public void ThenTheResultShouldBeTheDatabaseWasDeletedOnTheServer()
+ {
+ try
+ {
+ couchClient.GetDatabase(databaseName);
+ }
+ catch (CannotGetDatabaseException e)
+ {
+ Assert.That(e, Is.TypeOf(typeof(CannotGetDatabaseException)));
+ }
+ }
+ }
+}
View
2  src/SineSignal.Ottoman.AcceptanceSpecs/StepDefinitions/DeveloperConnectsToServerSteps.cs
@@ -12,7 +12,7 @@ namespace SineSignal.Ottoman.AcceptanceSpecs
public class DeveloperConnectsToServerSteps
{
private string address;
- private CouchClient couchClient;
+ private ICouchClient couchClient;
[Given("I have a CouchDB instance running at http://127.0.0.1:5984")]
public void GivenIHaveACouchDBInstanceRunningAtHttp127_0_0_15984()
View
70 src/SineSignal.Ottoman.Specs/CouchProxySpecs.cs
@@ -1,4 +1,5 @@
using System;
+using System.Net;
using NSubstitute;
using NUnit.Framework;
@@ -6,13 +7,14 @@
using SineSignal.Ottoman.Specs.Framework;
using SineSignal.Ottoman.Commands;
+using SineSignal.Ottoman.Exceptions;
using SineSignal.Ottoman.Http;
namespace SineSignal.Ottoman.Specs
{
public class CouchProxySpecs
{
- public class When_executing_a_post_couch_command : ConcernFor<CouchProxy>
+ public class When_executing_a_couch_command_with_a_message : ConcernFor<CouchProxy>
{
private Employee entity1;
private IRestClient restClient;
@@ -28,18 +30,19 @@ protected override void Given()
couchCommand.Route.Returns("some/path");
couchCommand.Operation.Returns(HttpMethod.Post);
couchCommand.Message.Returns(entity1);
+ couchCommand.SuccessStatusCode.Returns(HttpStatusCode.Created);
restResponse = new RestResponse<ResultStub>
{
ContentType = "application/json",
ContentLength = 5,
Content = "{\"status\":\"completed\"}",
- StatusCode = System.Net.HttpStatusCode.Created,
- StatusDescription = System.Net.HttpStatusCode.Created.ToString(),
+ StatusCode = HttpStatusCode.Created,
+ StatusDescription = HttpStatusCode.Created.ToString(),
ContentDeserialized = new ResultStub()
};
- restClient.Process<ResultStub>(Arg.Any<RestRequest>()).Returns(restResponse);
+ restClient.Process<ResultStub>(Arg.Any<RestRequest>(), Arg.Any<HttpStatusCode>()).Returns(restResponse);
}
public override CouchProxy CreateSystemUnderTest()
@@ -59,6 +62,65 @@ public void Should_call_process_on_rest_client_with_generated_rest_request()
return r.Path == "some/path" &&
r.Method == HttpMethod.Post &&
(r.Payload is Employee && r.Payload == entity1);
+ }), Arg.Is<HttpStatusCode>(h => {
+ return h == HttpStatusCode.Created;
+ }));
+ }
+
+ [Test]
+ public void Should_return_deserialized_object()
+ {
+ Assert.That(resultStub.Status, Is.EqualTo("completed"));
+ }
+ }
+
+ public class When_executing_a_couch_command_without_a_message : ConcernFor<CouchProxy>
+ {
+ private IRestClient restClient;
+ private ICouchCommand couchCommand;
+ private ResultStub resultStub;
+ private RestResponse<ResultStub> restResponse;
+
+ 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);
+
+ restResponse = new RestResponse<ResultStub>
+ {
+ ContentType = "application/json",
+ ContentLength = 5,
+ Content = "{\"status\":\"completed\"}",
+ StatusCode = HttpStatusCode.OK,
+ StatusDescription = HttpStatusCode.OK.ToString(),
+ ContentDeserialized = new ResultStub()
+ };
+
+ restClient.Process<ResultStub>(Arg.Any<RestRequest>(), Arg.Any<HttpStatusCode>()).Returns(restResponse);
+ }
+
+ public override CouchProxy CreateSystemUnderTest()
+ {
+ return new CouchProxy(restClient);
+ }
+
+ protected override void When()
+ {
+ resultStub = 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;
}));
}
View
355 src/SineSignal.Ottoman.Specs/Http/RestClientSpecs.cs
@@ -6,6 +6,7 @@
using NUnit.Framework.SyntaxHelpers;
using SineSignal.Ottoman.Specs.Framework;
+using SineSignal.Ottoman.Exceptions;
using SineSignal.Ottoman.Http;
using SineSignal.Ottoman.Serialization;
@@ -13,51 +14,71 @@ namespace SineSignal.Ottoman.Specs.Http
{
public class RestClientSpecs
{
- public class When_processing_a_post_request : ConcernFor<RestClient>
+ public class When_receiving_an_expected_status_code : ConcernFor<RestClient>
+ {
+ protected const string jsonType = "application/json";
+
+ protected Uri baseUri;
+ protected IHttpClient httpClient;
+ protected ISerializer serializer;
+ protected string acceptType;
+ protected string responseContentType;
+ protected string responseContent;
+ protected ResultStub contentDeserialized;
+ protected HttpStatusCode successStatusCode;
+ protected RestResponse<ResultStub> restResponse;
+
+ protected override void Given()
+ {
+ baseUri = new Uri("http://127.0.0.1:5984");
+ httpClient = Fake<IHttpClient>();
+ serializer = Fake<ISerializer>();
+ acceptType = jsonType;
+ responseContentType = jsonType;
+ responseContent = "{\"status\":\"completed\"}";
+ contentDeserialized = new ResultStub();
+
+ serializer.Deserialize<ResultStub>(Arg.Any<string>()).Returns(contentDeserialized);
+ }
+
+ public override RestClient CreateSystemUnderTest()
+ {
+ return new RestClient(baseUri, httpClient, serializer);
+ }
+ }
+
+ public class When_processing_a_post_request : When_receiving_an_expected_status_code
{
- private Uri baseUri;
- private IHttpClient httpClient;
- private RestRequest restRequest;
private Employee payload;
- private RestResponse<ResultStub> restResponse;
- private ISerializer serializer;
+ private RestRequest restRequest;
+ private string requestContentType;
private string requestContent;
- private string responseContent;
- private ResultStub contentDeserialized;
- private string jsonContentType;
protected override void Given()
{
- baseUri = new Uri("http://127.0.0.1:5984");
+ base.Given();
+
payload = new Employee { Name = "Bob", Login = "boblogin" };
restRequest = new RestRequest { Path = "some/path", Method = HttpMethod.Post, Payload = payload };
- requestContent = "{\"Id\":\"" + Guid.Empty + "\",\"Name\":\"\",\"Login\":\"\"}";
- responseContent = "{\"status\":\"completed\"}";
- jsonContentType = "application/json";
+ requestContentType = jsonType;
+ requestContent = "{\"Id\":\"" + Guid.Empty + "\",\"Name\":\"Bob\",\"Login\":\"boblogin\"}";
- serializer = Fake<ISerializer>();
serializer.Serialize(Arg.Any<object>()).Returns(requestContent);
- contentDeserialized = new ResultStub();
- serializer.Deserialize<ResultStub>(Arg.Any<string>()).Returns(contentDeserialized);
- httpClient = Fake<IHttpClient>();
+ successStatusCode = HttpStatusCode.Created;
+
httpClient.Send(Arg.Any<HttpRequest>()).Returns(new HttpResponse {
- ContentType = jsonContentType,
+ ContentType = responseContentType,
ContentLength = responseContent.Length,
Content = responseContent,
- StatusCode = HttpStatusCode.Created,
- StatusDescription = HttpStatusCode.Created.ToString()
+ StatusCode = successStatusCode,
+ StatusDescription = successStatusCode.ToString()
});
}
- public override RestClient CreateSystemUnderTest()
- {
- return new RestClient(baseUri, httpClient, serializer);
- }
-
protected override void When()
{
- restResponse = Sut.Process<ResultStub>(restRequest);
+ restResponse = Sut.Process<ResultStub>(restRequest, successStatusCode);
}
[Test]
@@ -75,9 +96,9 @@ public void Should_call_send_on_http_client_with_generated_http_request()
{
httpClient.Received().Send(Arg.Is<HttpRequest>(h => {
return h.Url == new Uri(baseUri.ToString() + restRequest.Path) &&
- h.Accept == jsonContentType &&
+ h.Accept == acceptType &&
h.Method == restRequest.Method &&
- h.ContentType == jsonContentType &&
+ h.ContentType == requestContentType &&
h.Content == requestContent &&
h.ContentLength == requestContent.Length;
}));
@@ -95,60 +116,183 @@ public void Should_call_deserialize_on_serializer_with_content_of_response()
public void Should_return_rest_response_generated_from_http_response()
{
Assert.That(restResponse.RestRequest, Is.EqualTo(restRequest));
- Assert.That(restResponse.ContentType, Is.EqualTo(jsonContentType));
+ Assert.That(restResponse.ContentType, Is.EqualTo(responseContentType));
Assert.That(restResponse.Content, Is.EqualTo(responseContent));
Assert.That(restResponse.ContentLength, Is.EqualTo(responseContent.Length));
- Assert.That(restResponse.StatusCode, Is.EqualTo(HttpStatusCode.Created));
- Assert.That(restResponse.StatusDescription, Is.EqualTo(HttpStatusCode.Created.ToString()));
+ Assert.That(restResponse.StatusCode, Is.EqualTo(successStatusCode));
+ Assert.That(restResponse.StatusDescription, Is.EqualTo(successStatusCode.ToString()));
Assert.That(restResponse.ContentDeserialized, Is.EqualTo(contentDeserialized));
}
}
- public class When_processing_a_get_request : ConcernFor<RestClient>
+ public class When_processing_a_get_request : When_receiving_an_expected_status_code
{
- private Uri baseUri;
- private IHttpClient httpClient;
private RestRequest restRequest;
- private RestResponse<ResultStub> restResponse;
- private ISerializer serializer;
private string requestContent;
private string requestContentType;
- private string responseContent;
- private ResultStub contentDeserialized;
- private string responseContentType;
protected override void Given()
{
- baseUri = new Uri("http://127.0.0.1:5984");
+ base.Given();
+
restRequest = new RestRequest { Path = "some/path", Method = HttpMethod.Get, Payload = null };
requestContent = "";
requestContentType = "";
- responseContent = "{\"status\":\"completed\"}";
- responseContentType = "application/json";
- serializer = Fake<ISerializer>();
- serializer.Serialize(Arg.Any<object>()).Returns(requestContent);
- contentDeserialized = new ResultStub();
- serializer.Deserialize<ResultStub>(Arg.Any<string>()).Returns(contentDeserialized);
+ successStatusCode = HttpStatusCode.OK;
- httpClient = Fake<IHttpClient>();
httpClient.Send(Arg.Any<HttpRequest>()).Returns(new HttpResponse {
ContentType = responseContentType,
ContentLength = responseContent.Length,
Content = responseContent,
- StatusCode = HttpStatusCode.Created,
- StatusDescription = HttpStatusCode.Created.ToString()
+ StatusCode = successStatusCode,
+ StatusDescription = successStatusCode.ToString()
});
}
- public override RestClient CreateSystemUnderTest()
+ protected override void When()
{
- return new RestClient(baseUri, httpClient, serializer);
+ restResponse = Sut.Process<ResultStub>(restRequest, successStatusCode);
}
-
+
+ [Test]
+ public void Should_not_call_serialize_on_serializer_with_the_payload()
+ {
+ serializer.DidNotReceive().Serialize(null);
+ }
+
+ [Test]
+ public void Should_call_send_on_http_client_with_generated_http_request()
+ {
+ httpClient.Received().Send(Arg.Is<HttpRequest>(h => {
+ return h.Url == new Uri(baseUri.ToString() + restRequest.Path) &&
+ h.Accept == responseContentType &&
+ h.Method == restRequest.Method &&
+ h.ContentType == requestContentType &&
+ h.Content == requestContent &&
+ h.ContentLength == requestContent.Length;
+ }));
+ }
+
+ [Test]
+ public void Should_call_deserialize_on_serializer_with_content_of_response()
+ {
+ serializer.Received().Deserialize<ResultStub>(Arg.Is<string>(s => {
+ return s == responseContent;
+ }));
+ }
+
+ [Test]
+ public void Should_return_rest_response_generated_from_http_response()
+ {
+ Assert.That(restResponse.RestRequest, Is.EqualTo(restRequest));
+ Assert.That(restResponse.ContentType, Is.EqualTo(responseContentType));
+ Assert.That(restResponse.Content, Is.EqualTo(responseContent));
+ Assert.That(restResponse.ContentLength, Is.EqualTo(responseContent.Length));
+ Assert.That(restResponse.StatusCode, Is.EqualTo(successStatusCode));
+ Assert.That(restResponse.StatusDescription, Is.EqualTo(successStatusCode.ToString()));
+ Assert.That(restResponse.ContentDeserialized, Is.EqualTo(contentDeserialized));
+ }
+ }
+
+ public class When_processing_a_put_request_with_no_payload : When_receiving_an_expected_status_code
+ {
+ private RestRequest restRequest;
+ private string requestContent;
+ private string requestContentType;
+
+ protected override void Given()
+ {
+ base.Given();
+
+ restRequest = new RestRequest { Path = "some/path", Method = HttpMethod.Put, Payload = null };
+ requestContent = "";
+ requestContentType = "";
+
+ successStatusCode = HttpStatusCode.Created;
+
+ httpClient.Send(Arg.Any<HttpRequest>()).Returns(new HttpResponse {
+ ContentType = responseContentType,
+ ContentLength = responseContent.Length,
+ Content = responseContent,
+ StatusCode = successStatusCode,
+ StatusDescription = successStatusCode.ToString()
+ });
+ }
+
+ protected override void When()
+ {
+ restResponse = Sut.Process<ResultStub>(restRequest, successStatusCode);
+ }
+
+ [Test]
+ public void Should_not_call_serialize_on_serializer_with_the_payload()
+ {
+ serializer.DidNotReceive().Serialize(null);
+ }
+
+ [Test]
+ public void Should_call_send_on_http_client_with_generated_http_request()
+ {
+ httpClient.Received().Send(Arg.Is<HttpRequest>(h => {
+ return h.Url == new Uri(baseUri.ToString() + restRequest.Path) &&
+ h.Accept == responseContentType &&
+ h.Method == restRequest.Method &&
+ h.ContentType == requestContentType &&
+ h.Content == requestContent &&
+ h.ContentLength == requestContent.Length;
+ }));
+ }
+
+ [Test]
+ public void Should_call_deserialize_on_serializer_with_content_of_response()
+ {
+ serializer.Received().Deserialize<ResultStub>(Arg.Is<string>(s => {
+ return s == responseContent;
+ }));
+ }
+
+ [Test]
+ public void Should_return_rest_response_generated_from_http_response()
+ {
+ Assert.That(restResponse.RestRequest, Is.EqualTo(restRequest));
+ Assert.That(restResponse.ContentType, Is.EqualTo(responseContentType));
+ Assert.That(restResponse.Content, Is.EqualTo(responseContent));
+ Assert.That(restResponse.ContentLength, Is.EqualTo(responseContent.Length));
+ Assert.That(restResponse.StatusCode, Is.EqualTo(successStatusCode));
+ Assert.That(restResponse.StatusDescription, Is.EqualTo(successStatusCode.ToString()));
+ Assert.That(restResponse.ContentDeserialized, Is.EqualTo(contentDeserialized));
+ }
+ }
+
+ public class When_processing_a_delete_request : When_receiving_an_expected_status_code
+ {
+ private RestRequest restRequest;
+ private string requestContent;
+ private string requestContentType;
+
+ protected override void Given()
+ {
+ base.Given();
+
+ restRequest = new RestRequest { Path = "some/path", Method = HttpMethod.Delete, Payload = null };
+ requestContent = "";
+ requestContentType = "";
+
+ successStatusCode = HttpStatusCode.OK;
+
+ httpClient.Send(Arg.Any<HttpRequest>()).Returns(new HttpResponse {
+ ContentType = responseContentType,
+ ContentLength = responseContent.Length,
+ Content = responseContent,
+ StatusCode = successStatusCode,
+ StatusDescription = successStatusCode.ToString()
+ });
+ }
+
protected override void When()
{
- restResponse = Sut.Process<ResultStub>(restRequest);
+ restResponse = Sut.Process<ResultStub>(restRequest, successStatusCode);
}
[Test]
@@ -185,10 +329,111 @@ public void Should_return_rest_response_generated_from_http_response()
Assert.That(restResponse.ContentType, Is.EqualTo(responseContentType));
Assert.That(restResponse.Content, Is.EqualTo(responseContent));
Assert.That(restResponse.ContentLength, Is.EqualTo(responseContent.Length));
- Assert.That(restResponse.StatusCode, Is.EqualTo(HttpStatusCode.Created));
- Assert.That(restResponse.StatusDescription, Is.EqualTo(HttpStatusCode.Created.ToString()));
+ Assert.That(restResponse.StatusCode, Is.EqualTo(successStatusCode));
+ Assert.That(restResponse.StatusDescription, Is.EqualTo(successStatusCode.ToString()));
Assert.That(restResponse.ContentDeserialized, Is.EqualTo(contentDeserialized));
}
}
}
+
+ public class When_receiving_an_unexpected_status_code : ConcernFor<RestClient>
+ {
+ private const string jsonType = "application/json";
+
+ private Uri baseUri;
+ private IHttpClient httpClient;
+ private ISerializer serializer;
+ private RestRequest restRequest;
+ private string requestContent;
+ private string requestContentType;
+ protected string responseContentType;
+ protected string responseContent;
+ private HttpStatusCode successStatusCode;
+ private HttpStatusCode unexpectedStatusCode;
+ private UnexpectedHttpResponseException unexpectedResponseException;
+
+ protected override void Given()
+ {
+ baseUri = new Uri("http://127.0.0.1:5984");
+ httpClient = Fake<IHttpClient>();
+ serializer = Fake<ISerializer>();
+
+ restRequest = new RestRequest { Path = "some/path", Method = HttpMethod.Get, Payload = null };
+ requestContent = "";
+ requestContentType = "";
+ responseContentType = jsonType;
+ responseContent = "{\"error\":\"not_found\",\"reason\":\"no_db_file\"}";
+
+ successStatusCode = HttpStatusCode.OK;
+ unexpectedStatusCode = HttpStatusCode.NotFound;
+
+ httpClient.Send(Arg.Any<HttpRequest>()).Returns(new HttpResponse {
+ ContentType = responseContentType,
+ ContentLength = responseContent.Length,
+ Content = responseContent,
+ StatusCode = unexpectedStatusCode,
+ StatusDescription = unexpectedStatusCode.ToString()
+ });
+ }
+
+ public override RestClient CreateSystemUnderTest ()
+ {
+ return new RestClient(baseUri, httpClient, serializer);
+ }
+
+ protected override void When()
+ {
+ try
+ {
+ Sut.Process<ResultStub>(restRequest, successStatusCode);
+ }
+ catch (UnexpectedHttpResponseException e)
+ {
+ unexpectedResponseException = e;
+ }
+ }
+
+ [Test]
+ public void Should_not_call_serialize_on_serializer_with_the_payload()
+ {
+ serializer.DidNotReceive().Serialize(null);
+ }
+
+ [Test]
+ public void Should_call_send_on_http_client_with_generated_http_request()
+ {
+ httpClient.Received().Send(Arg.Is<HttpRequest>(h => {
+ return h.Url == new Uri(baseUri.ToString() + restRequest.Path) &&
+ h.Accept == responseContentType &&
+ h.Method == restRequest.Method &&
+ h.ContentType == requestContentType &&
+ h.Content == requestContent &&
+ h.ContentLength == requestContent.Length;
+ }));
+ }
+
+ [Test]
+ public void Should_not_call_deserialize_on_serializer_with_content_of_response()
+ {
+ serializer.DidNotReceive().Deserialize<ResultStub>(Arg.Is<string>(s => {
+ return s == responseContent;
+ }));
+ }
+
+ [Test]
+ public void Should_throw_unexpected_http_response_exception()
+ {
+ Assert.That(unexpectedResponseException, Is.Not.Null);
+ }
+
+ [Test]
+ public void Should_populate_raw_response_on_unexpected_http_response_exception_with_http_response_returned_by_send()
+ {
+ Assert.That(unexpectedResponseException.RawResponse.ContentType, Is.EqualTo(responseContentType));
+ Assert.That(unexpectedResponseException.RawResponse.Content, Is.EqualTo(responseContent));
+ Assert.That(unexpectedResponseException.RawResponse.ContentLength, Is.EqualTo(responseContent.Length));
+ Assert.That(unexpectedResponseException.RawResponse.StatusCode, Is.EqualTo(unexpectedStatusCode));
+ Assert.That(unexpectedResponseException.RawResponse.StatusDescription, Is.EqualTo(unexpectedStatusCode.ToString()));
+ }
+ }
}
View
6 src/SineSignal.Ottoman/Commands/BulkDocsCommand.cs
@@ -1,7 +1,9 @@
using System;
using System.Collections.Generic;
using System.Linq;
+using System.Net;
+using SineSignal.Ottoman.Exceptions;
using SineSignal.Ottoman.Http;
namespace SineSignal.Ottoman.Commands
@@ -19,11 +21,15 @@ public object Message
get { return _message; }
}
+ public HttpStatusCode SuccessStatusCode { get; private set; }
+ public Action<CommandErrorResult, UnexpectedHttpResponseException> OnErrorHandler { get { throw new NotImplementedException(); } }
+
public BulkDocsCommand(string databaseName, BulkDocsMessage message)
{
Route = databaseName + "/_bulk_docs";
Operation = HttpMethod.Post;
_message = message;
+ SuccessStatusCode = HttpStatusCode.Created;
}
}
View
5 src/SineSignal.Ottoman/Commands/ConnectToServerCommand.cs
@@ -1,5 +1,7 @@
using System;
+using System.Net;
+using SineSignal.Ottoman.Exceptions;
using SineSignal.Ottoman.Http;
using SineSignal.Ottoman.Serialization;
@@ -10,12 +12,15 @@ public class ConnectToServerCommand : ICouchCommand
public string Route { get; private set; }
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()
{
Route = "/";
Operation = HttpMethod.Get;
Message = null;
+ SuccessStatusCode = HttpStatusCode.OK;
}
}
View
25 src/SineSignal.Ottoman/Commands/CreateDatabaseCommand.cs
@@ -0,0 +1,25 @@
+using System;
+using System.Net;
+
+using SineSignal.Ottoman.Exceptions;
+using SineSignal.Ottoman.Http;
+
+namespace SineSignal.Ottoman.Commands
+{
+ public class CreateDatabaseCommand : ICouchCommand
+ {
+ public string Route { get; private set; }
+ 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 CreateDatabaseCommand(string databaseName)
+ {
+ Route = databaseName;
+ Operation = HttpMethod.Put;
+ Message = null;
+ SuccessStatusCode = HttpStatusCode.Created;
+ }
+ }
+}
View
25 src/SineSignal.Ottoman/Commands/DeleteDatabaseCommand.cs
@@ -0,0 +1,25 @@
+using System;
+using System.Net;
+
+using SineSignal.Ottoman.Exceptions;
+using SineSignal.Ottoman.Http;
+
+namespace SineSignal.Ottoman.Commands
+{
+ public class DeleteDatabaseCommand : ICouchCommand
+ {
+ public string Route { get; private set; }
+ 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 DeleteDatabaseCommand(string databaseName)
+ {
+ Route = databaseName;
+ Operation = HttpMethod.Delete;
+ Message = null;
+ SuccessStatusCode = HttpStatusCode.OK;
+ }
+ }
+}
View
66 src/SineSignal.Ottoman/Commands/GetDatabaseCommand.cs
@@ -0,0 +1,66 @@
+using System;
+using System.Net;
+
+using SineSignal.Ottoman.Exceptions;
+using SineSignal.Ottoman.Http;
+using SineSignal.Ottoman.Serialization;
+
+namespace SineSignal.Ottoman.Commands
+{
+ public class GetDatabaseCommand : ICouchCommand
+ {
+ public string Route { get; private set; }
+ 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; }
+
+ public GetDatabaseCommand(string databaseName)
+ {
+ DatabaseName = databaseName;
+
+ Route = DatabaseName;
+ Operation = HttpMethod.Get;
+ Message = null;
+ SuccessStatusCode = HttpStatusCode.OK;
+ OnErrorHandler = OnError;
+ }
+
+ private void OnError(CommandErrorResult errorResult, UnexpectedHttpResponseException innerException)
+ {
+ throw new CannotGetDatabaseException(DatabaseName, errorResult, innerException);
+ }
+ }
+
+ public class GetDatabaseResult
+ {
+ [JsonMember("db_name")]
+ public string DatabaseName { get; set; }
+
+ [JsonMember("doc_count")]
+ public int DocumentCount { get; set; }
+
+ [JsonMember("doc_del_count")]
+ public int DeletedDocumentCount { get; set; }
+
+ [JsonMember("update_seq")]
+ public int CurrentUpdateCount { get; set; }
+
+ [JsonMember("purge_seq")]
+ public int CurrentPurgeCount { get; set; }
+
+ [JsonMember("compact_running")]
+ public bool CompactRunning { get; set; }
+
+ [JsonMember("disk_size")]
+ public int DiskSize { get; set; }
+
+ [JsonMember("instance_start_time")]
+ public string InstanceStartTime { get; set; }
+
+ [JsonMember("disk_format_version")]
+ public int DiskFormatVersion { get; set; }
+ }
+}
View
22 src/SineSignal.Ottoman/Commands/ICouchCommand.cs
@@ -1,4 +1,9 @@
using System;
+using System.Net;
+
+using SineSignal.Ottoman.Exceptions;
+using SineSignal.Ottoman.Http;
+using SineSignal.Ottoman.Serialization;
namespace SineSignal.Ottoman.Commands
{
@@ -7,5 +12,22 @@ public interface ICouchCommand
string Route { get; }
string Operation { get; }
object Message { get; }
+ HttpStatusCode SuccessStatusCode { get; }
+ Action<CommandErrorResult, UnexpectedHttpResponseException> OnErrorHandler { get; }
+ }
+
+ public class CommandDefaultResult
+ {
+ [JsonMember("ok")]
+ public bool Succeeded { get; set; }
+ }
+
+ public class CommandErrorResult
+ {
+ [JsonMember("error")]
+ public string Error { get; set; }
+
+ [JsonMember("reason")]
+ public string Reason { get; set; }
}
}
View
26 src/SineSignal.Ottoman/CouchClient.cs
@@ -4,11 +4,11 @@
namespace SineSignal.Ottoman
{
- public class CouchClient
+ public class CouchClient : ICouchClient
{
public string ServerVersion { get; private set; }
- private ICouchProxy CouchProxy { get; set; }
+ public ICouchProxy CouchProxy { get; private set; }
private CouchClient(ICouchProxy couchProxy, string serverVersion)
{
@@ -16,7 +16,27 @@ private CouchClient(ICouchProxy couchProxy, string serverVersion)
ServerVersion = serverVersion;
}
- public static CouchClient ConnectTo(string address)
+ public void CreateDatabase(string name)
+ {
+ var createDatabaseCommand = new CreateDatabaseCommand(name);
+ CouchProxy.Execute<CommandDefaultResult>(createDatabaseCommand);
+ }
+
+ public ICouchDatabase GetDatabase(string name)
+ {
+ var getDatabaseCommand = new GetDatabaseCommand(name);
+ GetDatabaseResult result = CouchProxy.Execute<GetDatabaseResult>(getDatabaseCommand);
+
+ return new CouchDatabase(this, result.DatabaseName);
+ }
+
+ public void DeleteDatabase(string name)
+ {
+ var deleteDatabaseCommand = new DeleteDatabaseCommand(name);
+ CouchProxy.Execute<CommandDefaultResult>(deleteDatabaseCommand);
+ }
+
+ public static ICouchClient ConnectTo(string address)
{
ICouchProxy couchProxy = new CouchProxy(new Uri(address));
ICouchCommand couchCommand = new ConnectToServerCommand();
View
27 src/SineSignal.Ottoman/CouchDatabase.cs
@@ -0,0 +1,27 @@
+using System;
+
+namespace SineSignal.Ottoman
+{
+ public class CouchDatabase : ICouchDatabase
+ {
+ private CouchClient CouchClient { get; set; }
+
+ public ICouchProxy CouchProxy
+ {
+ get { return CouchClient.CouchProxy; }
+ }
+
+ public IDocumentConvention DocumentConvention
+ {
+ get { throw new NotImplementedException(); }
+ }
+
+ public string Name { get; private set; }
+
+ public CouchDatabase(CouchClient couchClient, string name)
+ {
+ CouchClient = couchClient;
+ Name = name;
+ }
+ }
+}
View
17 src/SineSignal.Ottoman/CouchProxy.cs
@@ -1,7 +1,9 @@
using System;
using SineSignal.Ottoman.Commands;
+using SineSignal.Ottoman.Exceptions;
using SineSignal.Ottoman.Http;
+using SineSignal.Ottoman.Serialization;
namespace SineSignal.Ottoman
{
@@ -9,8 +11,11 @@ public class CouchProxy : ICouchProxy
{
public IRestClient RestClient { get; private set; }
+ private ISerializer Serializer { get; set; }
+
public CouchProxy(Uri serverLocation) : this(new RestClient(serverLocation))
{
+ Serializer = new JsonSerializer();
}
public CouchProxy(IRestClient restClient)
@@ -22,7 +27,17 @@ public TResult Execute<TResult>(ICouchCommand couchCommand)
{
var restRequest = CreateRestRequestFrom(couchCommand);
- RestResponse<TResult> restResponse = RestClient.Process<TResult>(restRequest);
+ RestResponse<TResult> restResponse = null;
+
+ try
+ {
+ restResponse = RestClient.Process<TResult>(restRequest, couchCommand.SuccessStatusCode);
+ }
+ catch (UnexpectedHttpResponseException e)
+ {
+ var errorResult = Serializer.Deserialize<CommandErrorResult>(e.RawResponse.Content);
+ couchCommand.OnErrorHandler(errorResult, e);
+ }
return restResponse.ContentDeserialized;
}
View
17 src/SineSignal.Ottoman/Exceptions/CannotGetDatabaseException.cs
@@ -0,0 +1,17 @@
+using System;
+
+using SineSignal.Ottoman.Commands;
+using SineSignal.Ottoman.Http;
+
+namespace SineSignal.Ottoman.Exceptions
+{
+ public class CannotGetDatabaseException : CouchException
+ {
+ private const string ExceptionMessageFormat = "Failed to get database '{0}'";
+
+ public CannotGetDatabaseException(string databaseName, CommandErrorResult errorResult, UnexpectedHttpResponseException innerException)
+ : base(String.Format(ExceptionMessageFormat, databaseName), errorResult, innerException)
+ {
+ }
+ }
+}
View
18 src/SineSignal.Ottoman/Exceptions/CouchException.cs
@@ -0,0 +1,18 @@
+using System;
+
+using SineSignal.Ottoman.Commands;
+using SineSignal.Ottoman.Http;
+
+namespace SineSignal.Ottoman.Exceptions
+{
+ public class CouchException : Exception
+ {
+ public CommandErrorResult CouchError { get; private set; }
+
+ public CouchException(string message, CommandErrorResult errorResult, UnexpectedHttpResponseException innerException)
+ : base(message, innerException)
+ {
+ CouchError = errorResult;
+ }
+ }
+}
View
20 src/SineSignal.Ottoman/Exceptions/UnexpectedHttpResponseException.cs
@@ -0,0 +1,20 @@
+using System;
+using System.Net;
+
+using SineSignal.Ottoman.Http;
+
+namespace SineSignal.Ottoman.Exceptions
+{
+ public class UnexpectedHttpResponseException : Exception
+ {
+ private const string ExceptionMessageFormat = "Received an unexpected response: Expected Status Code '{0}', Received Status Code '{1}'";
+
+ public HttpResponse RawResponse { get; private set; }
+
+ public UnexpectedHttpResponseException(HttpStatusCode expectedStatus, HttpResponse rawResponse) :
+ base(String.Format(ExceptionMessageFormat, expectedStatus, rawResponse.StatusCode))
+ {
+ RawResponse = rawResponse;
+ }
+ }
+}
View
4 src/SineSignal.Ottoman/Http/IRestClient.cs
@@ -1,7 +1,9 @@
+using System.Net;
+
namespace SineSignal.Ottoman.Http
{
public interface IRestClient
{
- RestResponse<T> Process<T>(RestRequest restRequest);
+ RestResponse<T> Process<T>(RestRequest restRequest, HttpStatusCode successStatusCode);
}
}
View
13 src/SineSignal.Ottoman/Http/RestClient.cs
@@ -1,5 +1,7 @@
using System;
+using System.Net;
+using SineSignal.Ottoman.Exceptions;
using SineSignal.Ottoman.Serialization;
namespace SineSignal.Ottoman.Http
@@ -24,12 +26,19 @@ public RestClient(Uri baseUri, IHttpClient httpClient, ISerializer serializer)
Serializer = serializer;
}
- public RestResponse<T> Process<T>(RestRequest restRequest)
+ public RestResponse<T> Process<T>(RestRequest restRequest, HttpStatusCode successStatusCode)
{
HttpRequest httpRequest = ConverToHttpRequestFrom(restRequest);
HttpResponse httpResponse = HttpClient.Send(httpRequest);
- return ConvertToRestResponseFrom<T>(httpResponse, restRequest);
+ if (httpResponse.StatusCode == successStatusCode)
+ {
+ return ConvertToRestResponseFrom<T>(httpResponse, restRequest);
+ }
+ else
+ {
+ throw new UnexpectedHttpResponseException(successStatusCode, httpResponse);
+ }
}
private HttpRequest ConverToHttpRequestFrom(RestRequest restRequest)
View
12 src/SineSignal.Ottoman/ICouchClient.cs
@@ -0,0 +1,12 @@
+using System;
+
+namespace SineSignal.Ottoman
+{
+ public interface ICouchClient
+ {
+ string ServerVersion { get; }
+ void CreateDatabase(string name);
+ ICouchDatabase GetDatabase(string name);
+ void DeleteDatabase(string name);
+ }
+}
View
8 src/SineSignal.Ottoman/SineSignal.Ottoman.csproj
@@ -62,6 +62,14 @@
<Compile Include="Serialization\JsonMemberAttribute.cs" />
<Compile Include="CouchClient.cs" />
<Compile Include="Commands\ConnectToServerCommand.cs" />
+ <Compile Include="Commands\CreateDatabaseCommand.cs" />
+ <Compile Include="ICouchClient.cs" />
+ <Compile Include="Commands\GetDatabaseCommand.cs" />
+ <Compile Include="CouchDatabase.cs" />
+ <Compile Include="Commands\DeleteDatabaseCommand.cs" />
+ <Compile Include="Exceptions\CannotGetDatabaseException.cs" />
+ <Compile Include="Exceptions\CouchException.cs" />
+ <Compile Include="Exceptions\UnexpectedHttpResponseException.cs" />
</ItemGroup>
<ItemGroup>
<Reference Include="System" />

0 comments on commit 578535b

Please sign in to comment.
Something went wrong with that request. Please try again.