diff --git a/.gitignore b/.gitignore index a3da8eb..c8a30a3 100644 --- a/.gitignore +++ b/.gitignore @@ -11,3 +11,4 @@ TestResults TestResult.xml /packages/ /publish/ +_ReSharper* diff --git a/BuildRevisionCounter.Tests/BuildRevisionCounter.Tests.csproj b/BuildRevisionCounter.Tests/BuildRevisionCounter.Tests.csproj index 8ce0b45..478d03e 100644 --- a/BuildRevisionCounter.Tests/BuildRevisionCounter.Tests.csproj +++ b/BuildRevisionCounter.Tests/BuildRevisionCounter.Tests.csproj @@ -17,6 +17,8 @@ False UnitTest + ..\ + true true @@ -90,12 +92,12 @@ + - - + + - diff --git a/BuildRevisionCounter.Tests/BuildRevisionCounterTest.cs b/BuildRevisionCounter.Tests/BuildRevisionCounterTest.cs index 6ebc80f..09bc438 100644 --- a/BuildRevisionCounter.Tests/BuildRevisionCounterTest.cs +++ b/BuildRevisionCounter.Tests/BuildRevisionCounterTest.cs @@ -11,7 +11,7 @@ public class BuildRevisionCounterTest : IntegrationTest [Test] public async Task GetAllRevisions() { - var apiUri = "api/counter"; + const string apiUri = "api/counter"; var body = await SendGetRequest(apiUri); dynamic result = JArray.Parse(body); diff --git a/BuildRevisionCounter.Tests/Controllers/CounterControllerTest.cs b/BuildRevisionCounter.Tests/Controllers/CounterControllerTest.cs index c4bfe67..c6e7eaf 100644 --- a/BuildRevisionCounter.Tests/Controllers/CounterControllerTest.cs +++ b/BuildRevisionCounter.Tests/Controllers/CounterControllerTest.cs @@ -2,6 +2,7 @@ using System.Threading.Tasks; using System.Web.Http; using BuildRevisionCounter.Controllers; +using BuildRevisionCounter.Data; using NUnit.Framework; namespace BuildRevisionCounter.Tests.Controllers @@ -14,9 +15,8 @@ public class CounterControllerTest [TestFixtureSetUp] public void SetUp() { - MongoDBStorageUtils.SetUpAsync().Wait(); - - _controller = new CounterController(MongoDBStorageFactory.DefaultInstance); + DBStorageFactory.DefaultInstance.SetUp().Wait(); + _controller = new CounterController(DBStorageUtil.GetRevisionStorage(connectionString: DBStorageFactory.DefaultInstance.ConnectionString)); } [Test] @@ -55,5 +55,11 @@ public async Task CurrentReturnsSameValueAsPreviousBumping() var rev2 = await _controller.Current("CurrentReturnSameValueAsPreviousBumping"); Assert.AreEqual(rev1, rev2); } + + [TestFixtureTearDown] + public void DropDatabaseAsync() + { + DBStorageFactory.DefaultInstance.DropDatabaseAsync().Wait(); + } } } \ No newline at end of file diff --git a/BuildRevisionCounter.Tests/DBStorageFactory.cs b/BuildRevisionCounter.Tests/DBStorageFactory.cs new file mode 100644 index 0000000..3945217 --- /dev/null +++ b/BuildRevisionCounter.Tests/DBStorageFactory.cs @@ -0,0 +1,40 @@ +using System; +using System.Configuration; +using BuildRevisionCounter.Data; +using BuildRevisionCounter.Interfaces; + +namespace BuildRevisionCounter.Tests +{ + public static class DBStorageFactory + { + private static readonly Lazy _defaultInstance = + new Lazy(() => FromConfigurationConnectionString()); + + public static IUserDatabaseTestProvider DefaultInstance { get { return _defaultInstance.Value; } } + + + public static IUserDatabaseTestProvider GetInstance(string connectionStringName = "MongoDBStorage") where T : IUserDatabaseTestProvider + { + return GetDatabaseTestProvider(connectionStringName); + } + + public static IUserDatabaseTestProvider FromConfigurationConnectionString(string connectionStringName = "MongoDBStorage") + { + return GetDatabaseTestProvider(connectionStringName); + } + + private static IUserDatabaseTestProvider GetDatabaseTestProvider(string connectionStringName) + { + var connectionString = ConfigurationManager.ConnectionStrings[connectionStringName].ConnectionString; + connectionString = DBUtil.SetDatabaseNameRandom(typeof(MongoDBUserStorage), connectionString); + return new MongoDBUserStorage(connectionString); + } + + private static IUserDatabaseTestProvider GetDatabaseTestProvider(string connectionStringName) where T : IUserDatabaseTestProvider + { + var connectionString = ConfigurationManager.ConnectionStrings[connectionStringName].ConnectionString; + connectionString = DBUtil.SetDatabaseNameRandom(typeof (T), connectionString); + return (IUserDatabaseTestProvider)Activator.CreateInstance(typeof(T), connectionString); + } + } +} \ No newline at end of file diff --git a/BuildRevisionCounter.Tests/MongoDBStorageTest.cs b/BuildRevisionCounter.Tests/DBStorageTest.cs similarity index 59% rename from BuildRevisionCounter.Tests/MongoDBStorageTest.cs rename to BuildRevisionCounter.Tests/DBStorageTest.cs index c498db1..62b28d0 100644 --- a/BuildRevisionCounter.Tests/MongoDBStorageTest.cs +++ b/BuildRevisionCounter.Tests/DBStorageTest.cs @@ -1,13 +1,14 @@ using System.Threading.Tasks; -using MongoDB.Driver; +using BuildRevisionCounter.Data; +using BuildRevisionCounter.Interfaces; using NUnit.Framework; namespace BuildRevisionCounter.Tests { [TestFixture] - public class MongoDBStorageTest + public class DBStorageTest { - private MongoDBStorage _storage; + private IUserDatabaseTestProvider _storage; [TestFixtureSetUp] public void SetUp() @@ -17,19 +18,17 @@ public void SetUp() public async Task SetUpAsync() { - _storage = MongoDBStorageFactory.DefaultInstance; - - await _storage.Revisions.Database.Client.DropDatabaseAsync( - _storage.Revisions.Database.DatabaseNamespace.DatabaseName); - + _storage = DBStorageFactory.GetInstance(); await _storage.SetUp(); } [Test] public async Task EnsureAdminUserCreated() { - var user = await _storage.FindUser(MongoDBStorage.AdminName); - Assert.AreEqual(MongoDBStorage.AdminName, user.Name); + var adminName = _storage.GetAdminName(); + var user = await _storage.FindUser(adminName); + Assert.IsNotNull(user); + Assert.AreEqual(adminName, user.Name); } [Test] @@ -63,23 +62,24 @@ public async Task EnsureAdminUserMayBeInvokedMultipleTimes() [Test] public async Task CreateUserMustThrowExceptionIfUserExists() { - await _storage.CreateUser( - "CreateUserMustThrowExceptionIfUserExists", - "CreateUserMustThrowExceptionIfUserExists", - new[] {"testRole"}); - + const string userName = "CreateUserMustThrowExceptionIfUserExists"; + const string userPass = "CreateUserMustThrowExceptionIfUserExists"; + var userRole = new[] {"testRole"}; + await _storage.CreateUser(userName, userPass, userRole); try { - await _storage.CreateUser( - "CreateUserMustThrowExceptionIfUserExists", - "CreateUserMustThrowExceptionIfUserExists", - new[] {"testRole"}); + await _storage.CreateUser(userName, userPass, userRole); Assert.Fail(); } - catch (MongoWriteException ex) + catch (DuplicateKeyException) { - Assert.AreEqual(ServerErrorCategory.DuplicateKey, ex.WriteError.Category); } } + + [TestFixtureTearDown] + public void DropDatabaseAsync() + { + _storage.DropDatabaseAsync().Wait(); + } } } \ No newline at end of file diff --git a/BuildRevisionCounter.Tests/DBUtil.cs b/BuildRevisionCounter.Tests/DBUtil.cs new file mode 100644 index 0000000..57ad5a9 --- /dev/null +++ b/BuildRevisionCounter.Tests/DBUtil.cs @@ -0,0 +1,62 @@ +using System; +using System.Threading.Tasks; +using BuildRevisionCounter.Data; +using BuildRevisionCounter.Interfaces; +using MongoDB.Driver; + +namespace BuildRevisionCounter.Tests +{ + internal static class DBUtil + { + internal static Task DropDatabaseAsync(this IUserDatabaseTestProvider dataProvider) + { + switch (GetDatabaseKind(dataProvider.GetType())) + { + case DatabaseKind.MongoDB: + return MangoDBDropDatabaseAsync(dataProvider.ConnectionString); + default: + throw new NotImplementedException(); + } + } + + internal static string SetDatabaseNameRandom(Type storageType, string connectionString) + { + switch (GetDatabaseKind(storageType)) + { + case DatabaseKind.MongoDB: + return MangoDBSetDatabaseNameRandom(connectionString); + default: + throw new NotImplementedException(); + } + } + + private static string MangoDBSetDatabaseNameRandom(string connectionString) + { + var urlBuilder = new MongoUrlBuilder(connectionString) + { + DatabaseName = "brCounterTest_" + Guid.NewGuid().ToString("N") + }; + return urlBuilder.ToString(); + } + + private static Task MangoDBDropDatabaseAsync(string connectionString) + { + var mongoUrl = MongoUrl.Create(connectionString); + return new MongoClient(mongoUrl).DropDatabaseAsync(mongoUrl.DatabaseName); + } + + + private enum DatabaseKind + { + Unknow, + MongoDB + } + + private static DatabaseKind GetDatabaseKind(Type storageType) + { + if (storageType == typeof(MongoDBUserStorage)) + return DatabaseKind.MongoDB; + return DatabaseKind.Unknow; + } + } +} diff --git a/BuildRevisionCounter.Tests/IntegrationTest/IntegrationTest.cs b/BuildRevisionCounter.Tests/IntegrationTest/IntegrationTest.cs index 08658a6..fa37151 100644 --- a/BuildRevisionCounter.Tests/IntegrationTest/IntegrationTest.cs +++ b/BuildRevisionCounter.Tests/IntegrationTest/IntegrationTest.cs @@ -1,11 +1,14 @@ using System; using System.Collections.Generic; +using System.Configuration; using System.Net; using System.Net.Http; using System.Net.Http.Headers; using System.Net.Sockets; +using System.Reflection; using System.Text; using System.Threading.Tasks; +using BuildRevisionCounter.Data; using Microsoft.Owin.Hosting; using NUnit.Framework; @@ -23,18 +26,28 @@ public abstract class IntegrationTest [TestFixtureSetUp] public void Setup() { + ChangeConnectionStringInConfigurationManager("MongoDBStorage", DBStorageFactory.DefaultInstance.ConnectionString); + var port = GetFreeTcpPort(); _uri = string.Format("http://localhost:{0}", port); _application = WebApp.Start(_uri); - MongoDBStorageUtils.SetUpAsync().Wait(); + DBStorageFactory.DefaultInstance.SetUp().Wait(); } + private static void ChangeConnectionStringInConfigurationManager(string connectionStringName, string connectionString) + { + var settings = ConfigurationManager.ConnectionStrings[connectionStringName]; + var fi = typeof (ConfigurationElement).GetField("_bReadOnly", BindingFlags.Instance | BindingFlags.NonPublic); + fi.SetValue(settings, false); + settings.ConnectionString = connectionString; + } [TestFixtureTearDown] public void TearDown() { _application.Dispose(); + DBStorageFactory.DefaultInstance.DropDatabaseAsync().Wait(); } private static int GetFreeTcpPort() @@ -72,14 +85,20 @@ protected async Task SendPostRequest(string apiUri, string userName = "a string.Format("{0}:{1}", userName, password)))); var content = new FormUrlEncodedContent(new[] - { - new KeyValuePair("", "") - }); + { + new KeyValuePair("", "") + }); var responseMessage = await HttpClient.PostAsync(apiUri, content); return await responseMessage.Content.ReadAsStringAsync(); } } + + [TestFixtureTearDown] + public void DropDatabaseAsync() + { + DBStorageFactory.DefaultInstance.DropDatabaseAsync().Wait(); + } } } diff --git a/BuildRevisionCounter.Tests/MongoDBStorageFactory.cs b/BuildRevisionCounter.Tests/MongoDBStorageFactory.cs deleted file mode 100644 index 9a072dd..0000000 --- a/BuildRevisionCounter.Tests/MongoDBStorageFactory.cs +++ /dev/null @@ -1,27 +0,0 @@ -using System; -using System.Configuration; -using MongoDB.Driver; - -namespace BuildRevisionCounter.Tests -{ - public static class MongoDBStorageFactory - { - private static readonly Lazy _defaultInstance = - new Lazy(() => FromConfigurationConnectionString()); - - public static MongoDBStorage DefaultInstance { get { return _defaultInstance.Value; } } - - public static MongoDBStorage FromConnectionString(string connectionString) - { - var mongoUrl = MongoUrl.Create(connectionString); - var database = new MongoClient(mongoUrl).GetDatabase(mongoUrl.DatabaseName); - return new MongoDBStorage(database); - } - - public static MongoDBStorage FromConfigurationConnectionString(string connectionStringName = "MongoDBStorage") - { - var connectionString = ConfigurationManager.ConnectionStrings[connectionStringName].ConnectionString; - return FromConnectionString(connectionString); - } - } -} \ No newline at end of file diff --git a/BuildRevisionCounter.Tests/MongoDBStorageUtils.cs b/BuildRevisionCounter.Tests/MongoDBStorageUtils.cs deleted file mode 100644 index 11759ee..0000000 --- a/BuildRevisionCounter.Tests/MongoDBStorageUtils.cs +++ /dev/null @@ -1,17 +0,0 @@ -using System; -using System.Threading.Tasks; - -namespace BuildRevisionCounter.Tests -{ - internal class MongoDBStorageUtils - { - public static async Task SetUpAsync() - { - var storage = MongoDBStorageFactory.DefaultInstance; - await storage.Revisions.Database.Client.DropDatabaseAsync( - storage.Revisions.Database.DatabaseNamespace.DatabaseName); - - await storage.SetUp(); - } - } -} diff --git a/BuildRevisionCounter/BuildRevisionCounter.csproj b/BuildRevisionCounter/BuildRevisionCounter.csproj index 596f9ab..c3bc259 100644 --- a/BuildRevisionCounter/BuildRevisionCounter.csproj +++ b/BuildRevisionCounter/BuildRevisionCounter.csproj @@ -20,6 +20,8 @@ + ..\ + true true @@ -113,17 +115,24 @@ - + + Designer + - + + + + + + - + diff --git a/BuildRevisionCounter/Controllers/CounterController.cs b/BuildRevisionCounter/Controllers/CounterController.cs index ae9e355..bf8df86 100644 --- a/BuildRevisionCounter/Controllers/CounterController.cs +++ b/BuildRevisionCounter/Controllers/CounterController.cs @@ -6,7 +6,7 @@ using BuildRevisionCounter.Interfaces; using BuildRevisionCounter.Model; using BuildRevisionCounter.Security; -using MongoDB.Driver; +using BuildRevisionCounter.Data; namespace BuildRevisionCounter.Controllers { @@ -14,18 +14,18 @@ namespace BuildRevisionCounter.Controllers [BasicAuthentication] public class CounterController : ApiController { - private readonly IMongoDBStorage _mongoDbStorage; + private readonly IRevisionStorage _dataStorage; /// /// Конструктор контроллера номеров ревизий. /// - /// Объект для получения данных из БД Монго. - public CounterController(IMongoDBStorage mongoDbStorage) + /// Объект для получения данных из БД. + public CounterController(IRevisionStorage dataStorage) { - if (mongoDbStorage == null) - throw new ArgumentNullException("mongoDbStorage"); + if (dataStorage == null) + throw new ArgumentNullException("dataStorage"); - _mongoDbStorage = mongoDbStorage; + _dataStorage = dataStorage; } [HttpGet] @@ -36,11 +36,7 @@ public async Task> GetAllRevision([FromUri] I if (pageSize < 1 || pageNumber < 1) throw new HttpResponseException(HttpStatusCode.BadRequest); - var revisions = await _mongoDbStorage.Revisions - .Find(r => true) - .Skip(pageSize * (pageNumber - 1)) - .Limit(pageSize) - .ToListAsync(); + var revisions = await _dataStorage.GetAllRevision(pageSize, pageNumber); if (revisions == null) throw new HttpResponseException(HttpStatusCode.NotFound); @@ -53,14 +49,12 @@ public async Task> GetAllRevision([FromUri] I [Authorize(Roles = "admin, editor, anonymous")] public async Task Current([FromUri] string revisionName) { - var revision = await _mongoDbStorage.Revisions - .Find(r => r.Id == revisionName) - .SingleOrDefaultAsync(); + var revisionNumber = await _dataStorage.CurrentRevision(revisionName); - if (revision == null) + if (revisionNumber == null) throw new HttpResponseException(HttpStatusCode.NotFound); - return revision.CurrentNumber; + return revisionNumber.Value; } [HttpPost] @@ -68,58 +62,7 @@ public async Task Current([FromUri] string revisionName) [Authorize(Roles = "buildserver")] public async Task Bumping([FromUri] string revisionName) { - // попробуем обновить документ - var result = await FindOneAndUpdateRevisionModelAsync(revisionName); - if (result != null) - return result.CurrentNumber; - - // если не получилось, значит документ еще не был создан - // создадим его с начальным значением 0 - try - { - await _mongoDbStorage.Revisions - .InsertOneAsync(new RevisionModel - { - Id = revisionName, - CurrentNumber = 0, - Created = DateTime.UtcNow - }); - return 0; - } - catch (MongoWriteException ex) - { - if (ex.WriteError.Category != ServerErrorCategory.DuplicateKey) - throw; - } - - // если при вставке произошла ошибка значит мы не успели и запись там уже есть - // и теперь попытка обновления должна пройти без ошибок - result = await FindOneAndUpdateRevisionModelAsync(revisionName); - - return result.CurrentNumber; - } - - /// - /// Инкриментит каунтер в БД - /// - /// - /// - private async Task FindOneAndUpdateRevisionModelAsync(string revisionName) - { - var result = await _mongoDbStorage.Revisions - .FindOneAndUpdateAsync( - r => r.Id == revisionName, - Builders.Update - .Inc(r => r.CurrentNumber, 1) - .SetOnInsert(r => r.Created, DateTime.UtcNow) - .Set(r => r.Updated, DateTime.UtcNow), - new FindOneAndUpdateOptions - { - IsUpsert = false, - ReturnDocument = ReturnDocument.After - }); - - return result; + return await _dataStorage.Bumping(revisionName); } } } \ No newline at end of file diff --git a/BuildRevisionCounter/Data/DBStorageUtil.cs b/BuildRevisionCounter/Data/DBStorageUtil.cs new file mode 100644 index 0000000..e73e97e --- /dev/null +++ b/BuildRevisionCounter/Data/DBStorageUtil.cs @@ -0,0 +1,39 @@ +using System.Threading.Tasks; +using System.Configuration; +using BuildRevisionCounter.Interfaces; + +namespace BuildRevisionCounter.Data +{ + public static class DBStorageUtil + { + + public static string GetAdminName(this IUserDatabaseTestProvider dataProvider) + { + return "admin"; + } + + private const string AdminPassword = "admin"; + + private static readonly string[] AdminRoles = { "admin", "buildserver", "editor" }; + + public static IRevisionStorage GetRevisionStorage(string connectionStringName = "MongoDBStorage", string connectionString = null) + { + connectionString = connectionString ?? ConfigurationManager.ConnectionStrings[connectionStringName].ConnectionString; + return new MongoDBRevisionStorage(connectionString); + } + + public static IUserStorage GetUserStorage(string connectionStringName = "MongoDBStorage") + { + var connectionString = ConfigurationManager.ConnectionStrings[connectionStringName].ConnectionString; + return new MongoDBUserStorage(connectionString); + } + + public static async Task EnsureAdminUser(this IUserDatabaseTestProvider dataProvider) + { + if (await dataProvider.FindUser(dataProvider.GetAdminName()) == null) + { + await dataProvider.CreateUser(dataProvider.GetAdminName(), AdminPassword, AdminRoles); + } + } + } +} \ No newline at end of file diff --git a/BuildRevisionCounter/Data/DuplicateKeyException.cs b/BuildRevisionCounter/Data/DuplicateKeyException.cs new file mode 100644 index 0000000..5b8174f --- /dev/null +++ b/BuildRevisionCounter/Data/DuplicateKeyException.cs @@ -0,0 +1,9 @@ +using System; + +namespace BuildRevisionCounter.Data +{ + [Serializable] + public class DuplicateKeyException : InvalidOperationException + { + } +} \ No newline at end of file diff --git a/BuildRevisionCounter/Data/MongoDBRevisionStorage.cs b/BuildRevisionCounter/Data/MongoDBRevisionStorage.cs new file mode 100644 index 0000000..eb96a80 --- /dev/null +++ b/BuildRevisionCounter/Data/MongoDBRevisionStorage.cs @@ -0,0 +1,109 @@ +using System; +using System.Collections.Generic; +using System.Reflection; +using System.Threading.Tasks; +using BuildRevisionCounter.Interfaces; +using BuildRevisionCounter.Model; +using BuildRevisionCounter.Security; +using MongoDB.Driver; + +namespace BuildRevisionCounter.Data +{ + public class MongoDBRevisionStorage : IRevisionStorage + { + private readonly IMongoCollection _revisions; + + public string ConnectionString { get; private set; } + + public MongoDBRevisionStorage(string connectionString) + { + ConnectionString = connectionString; + + var mongoUrl = MongoUrl.Create(connectionString); + var database = new MongoClient(mongoUrl).GetDatabase(mongoUrl.DatabaseName); + + _revisions = database.GetCollection("revisions"); + } + + public async Task> GetAllRevision(Int32 pageSize, Int32 pageNumber) + { + var revisions = await _revisions + .Find(r => true) + .Skip(pageSize * (pageNumber - 1)) + .Limit(pageSize) + .ToListAsync(); + + return revisions; + } + + public async Task CurrentRevision(string revisionName) + { + var revision = await _revisions + .Find(r => r.Id == revisionName) + .SingleOrDefaultAsync(); + + if (revision == null) + return null; + + return revision.CurrentNumber; + } + + public async Task Bumping(string revisionName) + { + // попробуем обновить документ + var result = await FindOneAndUpdateRevisionModelAsync(revisionName); + if (result != null) + return result.CurrentNumber; + + // если не получилось, значит документ еще не был создан + // создадим его с начальным значением 0 + if (await RevisionInsertAsync(revisionName)) + return 0; + + // если при вставке произошла ошибка значит мы не успели и запись там уже есть + // и теперь попытка обновления должна пройти без ошибок + result = await FindOneAndUpdateRevisionModelAsync(revisionName); + + return result.CurrentNumber; + } + + private async Task RevisionInsertAsync(string revisionName) + { + try + { + await _revisions + .InsertOneAsync(new RevisionModel + { + Id = revisionName, + CurrentNumber = 0, + Created = DateTime.UtcNow + }); + return true; + } + catch (MongoWriteException ex) + { + if (ex.WriteError != null && ex.WriteError.Category == ServerErrorCategory.DuplicateKey) + return false; + throw; + } + } + + private async Task FindOneAndUpdateRevisionModelAsync(string revisionName) + { + var result = await _revisions + .FindOneAndUpdateAsync( + r => r.Id == revisionName, + Builders.Update + .Inc(r => r.CurrentNumber, 1) + .SetOnInsert(r => r.Created, DateTime.UtcNow) + .Set(r => r.Updated, DateTime.UtcNow), + new FindOneAndUpdateOptions + { + IsUpsert = false, + ReturnDocument = ReturnDocument.After + }); + + return result; + } + } +} \ No newline at end of file diff --git a/BuildRevisionCounter/Data/MongoDBUserStorage.cs b/BuildRevisionCounter/Data/MongoDBUserStorage.cs new file mode 100644 index 0000000..d5b6767 --- /dev/null +++ b/BuildRevisionCounter/Data/MongoDBUserStorage.cs @@ -0,0 +1,73 @@ +using System.Threading.Tasks; +using BuildRevisionCounter.Interfaces; +using BuildRevisionCounter.Model; +using MongoDB.Driver; + +namespace BuildRevisionCounter.Data +{ + public class MongoDBUserStorage : IUserStorage, IUserDatabaseTestProvider + { + private readonly IMongoCollection _users; + + public string ConnectionString { get; private set; } + + public MongoDBUserStorage(string connectionString) + { + ConnectionString = connectionString; + + var mongoUrl = MongoUrl.Create(connectionString); + var database = new MongoClient(mongoUrl).GetDatabase(mongoUrl.DatabaseName); + + _users = database.GetCollection("users"); + + Task.Run(() => SetUp()).Wait(); + } + + public async Task SetUp() + { + await EnsureUsersIndex(); + await this.EnsureAdminUser(); + } + + private async Task EnsureUsersIndex() + { + await _users.Indexes.CreateOneAsync( + Builders.IndexKeys.Ascending(u => u.Name), + new CreateIndexOptions {Unique = true,}); + } + + public async Task FindUser(string name) + { + return await _users.Find(u => u.Name == name).SingleOrDefaultAsync(); + } + + /// + /// создать пользователя + /// + /// имя + /// пароль + /// роли + /// + /// В случае дублирования имени пользователя + public async Task CreateUser(string name, string password, string[] roles) + { + try + { + await _users + .InsertOneAsync( + new UserModel + { + Name = name, + Password = password, + Roles = roles + }); + } + catch (MongoWriteException ex) + { + if (ex.WriteError != null && ex.WriteError.Category == ServerErrorCategory.DuplicateKey) + throw new DuplicateKeyException(); + throw; + } + } + } +} \ No newline at end of file diff --git a/BuildRevisionCounter/Interfaces/IMongoDBStorage.cs b/BuildRevisionCounter/Interfaces/IMongoDBStorage.cs deleted file mode 100644 index c75dc23..0000000 --- a/BuildRevisionCounter/Interfaces/IMongoDBStorage.cs +++ /dev/null @@ -1,21 +0,0 @@ -using BuildRevisionCounter.Model; -using MongoDB.Driver; - -namespace BuildRevisionCounter.Interfaces -{ - /// - /// Интерфейс для получения данных из БД Монго. - /// - public interface IMongoDBStorage - { - /// - /// MongoDB-коллекция ревизий. - /// - IMongoCollection Revisions { get; } - - /// - /// MongoDB-коллекция пользователей. - /// - IMongoCollection Users { get; } - } -} \ No newline at end of file diff --git a/BuildRevisionCounter/Interfaces/IRevisionStorage.cs b/BuildRevisionCounter/Interfaces/IRevisionStorage.cs new file mode 100644 index 0000000..fd27f5f --- /dev/null +++ b/BuildRevisionCounter/Interfaces/IRevisionStorage.cs @@ -0,0 +1,19 @@ +using System; +using System.Collections.Generic; +using System.Threading.Tasks; +using BuildRevisionCounter.Model; + +namespace BuildRevisionCounter.Interfaces +{ + /// + /// Интерфейс для получения данных из БД. + /// + public interface IRevisionStorage + { + Task> GetAllRevision(Int32 pageSize, Int32 pageNumber); + + Task CurrentRevision(string revisionName); + + Task Bumping(string revisionName); + } +} \ No newline at end of file diff --git a/BuildRevisionCounter/Interfaces/IUserDatabaseTestProvider.cs b/BuildRevisionCounter/Interfaces/IUserDatabaseTestProvider.cs new file mode 100644 index 0000000..276623f --- /dev/null +++ b/BuildRevisionCounter/Interfaces/IUserDatabaseTestProvider.cs @@ -0,0 +1,19 @@ +using System.Threading.Tasks; +using BuildRevisionCounter.Model; + +namespace BuildRevisionCounter.Interfaces +{ + /// + /// Интерфейс для проверки операций с БД. + /// + public interface IUserDatabaseTestProvider + { + string ConnectionString { get; } + + Task SetUp(); + + Task FindUser(string name); + + Task CreateUser(string name, string password, string[] roles); + } +} \ No newline at end of file diff --git a/BuildRevisionCounter/Interfaces/IUserStorage.cs b/BuildRevisionCounter/Interfaces/IUserStorage.cs new file mode 100644 index 0000000..d8384ef --- /dev/null +++ b/BuildRevisionCounter/Interfaces/IUserStorage.cs @@ -0,0 +1,12 @@ +using System.Threading.Tasks; +using BuildRevisionCounter.Model; + +namespace BuildRevisionCounter.Interfaces +{ + public interface IUserStorage + { + Task FindUser(string name); + + Task CreateUser(string name, string password, string[] roles); + } +} \ No newline at end of file diff --git a/BuildRevisionCounter/Model/RevisionModel.cs b/BuildRevisionCounter/Model/RevisionModel.cs index 96adbbd..a3175ca 100644 --- a/BuildRevisionCounter/Model/RevisionModel.cs +++ b/BuildRevisionCounter/Model/RevisionModel.cs @@ -1,8 +1,5 @@ using MongoDB.Bson.Serialization.Attributes; using System; -using System.Collections.Generic; -using System.Linq; -using System.Web; namespace BuildRevisionCounter.Model { diff --git a/BuildRevisionCounter/MongoDBStorage.cs b/BuildRevisionCounter/MongoDBStorage.cs deleted file mode 100644 index f5d9c84..0000000 --- a/BuildRevisionCounter/MongoDBStorage.cs +++ /dev/null @@ -1,63 +0,0 @@ -using System.Threading.Tasks; -using BuildRevisionCounter.Interfaces; -using BuildRevisionCounter.Model; -using MongoDB.Driver; - -namespace BuildRevisionCounter -{ - public class MongoDBStorage : IMongoDBStorage - { - public static readonly string AdminName = "admin"; - public static readonly string AdminPassword = "admin"; - public static readonly string[] AdminRoles = {"admin", "buildserver", "editor"}; - - public IMongoCollection Revisions { get; private set; } - public IMongoCollection Users { get; private set; } - - public MongoDBStorage(IMongoDatabase database) - { - Revisions = database.GetCollection("revisions"); - Users = database.GetCollection("users"); - - Task.Run(() => SetUp()).Wait(); - } - - public async Task SetUp() - { - await EnsureUsersIndex(); - await EnsureAdminUser(); - } - - public async Task EnsureUsersIndex() - { - await Users.Indexes.CreateOneAsync( - Builders.IndexKeys.Ascending(u => u.Name), - new CreateIndexOptions { Unique = true, }); - } - - public async Task EnsureAdminUser() - { - if (await Users.CountAsync(_ => true) == 0) - { - await CreateUser(AdminName, AdminPassword, AdminRoles); - } - } - - public async Task FindUser(string name) - { - return await Users.Find(u => u.Name == name).SingleOrDefaultAsync(); - } - - public Task CreateUser(string name, string password, string[] roles) - { - return Users - .InsertOneAsync( - new UserModel - { - Name = name, - Password = password, - Roles = roles - }); - } - } -} \ No newline at end of file diff --git a/BuildRevisionCounter/Security/BasicAuthenticationFilter.cs b/BuildRevisionCounter/Security/BasicAuthenticationFilter.cs index e8a5228..b25af5e 100644 --- a/BuildRevisionCounter/Security/BasicAuthenticationFilter.cs +++ b/BuildRevisionCounter/Security/BasicAuthenticationFilter.cs @@ -7,8 +7,6 @@ using System.Threading.Tasks; using System.Web.Http.Filters; using BuildRevisionCounter.Interfaces; -using BuildRevisionCounter.Model; -using MongoDB.Driver; namespace BuildRevisionCounter.Security { @@ -29,18 +27,18 @@ public class BasicAuthenticationFilter : IAuthenticationFilter EncoderFallback.ExceptionFallback, DecoderFallback.ExceptionFallback); - private readonly IMongoDBStorage _mongoDbStorage; + private readonly IUserStorage _dataStorage; /// /// Конструктор фильтра. /// - /// Объект для получения данных из БД Монго. - public BasicAuthenticationFilter(IMongoDBStorage mongoDbStorage) + /// Объект для получения данных из БД. + public BasicAuthenticationFilter(IUserStorage dataStorage) { - if (mongoDbStorage == null) - throw new ArgumentNullException("mongoDbStorage"); + if (dataStorage == null) + throw new ArgumentNullException("dataStorage"); - _mongoDbStorage = mongoDbStorage; + _dataStorage = dataStorage; } public string Realm { get; set; } @@ -89,10 +87,7 @@ public Task ChallengeAsync(HttpAuthenticationChallengeContext context, Cancellat private async Task Authenticate(string userName, string password) { IPrincipal principal = null; - var user = - await - _mongoDbStorage.Users.Find(Builders.Filter.Where(u => u.Name == userName)) - .SingleOrDefaultAsync(); + var user = await _dataStorage.FindUser(userName); if (user != null && user.Password == password) { principal = new GenericPrincipal(new GenericIdentity(userName), user.Roles); diff --git a/BuildRevisionCounter/Startup.cs b/BuildRevisionCounter/Startup.cs index fc77cfa..0b7acfd 100644 --- a/BuildRevisionCounter/Startup.cs +++ b/BuildRevisionCounter/Startup.cs @@ -1,8 +1,8 @@ -using System.Configuration; -using System.Web.Http.Filters; +using System.Web.Http.Filters; +using BuildRevisionCounter.Data; +using BuildRevisionCounter.Interfaces; using BuildRevisionCounter.Security; using Microsoft.Owin; -using MongoDB.Driver; using Ninject; using Ninject.Web.Common.OwinHost; using Ninject.Web.WebApi.FilterBindingSyntax; @@ -11,7 +11,6 @@ using BuildRevisionCounter; using System.Web.Http; using System.Net.Http.Formatting; -using BuildRevisionCounter.Interfaces; [assembly: OwinStartup(typeof(Startup))] @@ -58,16 +57,9 @@ private static IKernel CreateKernel() /// Ядро Ninject. private static void RegisterServices(IKernel kernel) { - kernel.Bind().ToMethod(c => GetMongoDbStorage()).InSingletonScope(); + kernel.Bind().ToMethod(c => DBStorageUtil.GetRevisionStorage()).InSingletonScope(); + kernel.Bind().ToMethod(c => DBStorageUtil.GetUserStorage()).InSingletonScope(); kernel.BindHttpFilter(FilterScope.Controller).WhenControllerHas(); } - - private static MongoDBStorage GetMongoDbStorage(string connectionStringName = "MongoDBStorage") - { - var connectionString = ConfigurationManager.ConnectionStrings[connectionStringName].ConnectionString; - var mongoUrl = MongoUrl.Create(connectionString); - var database = new MongoClient(mongoUrl).GetDatabase(mongoUrl.DatabaseName); - return new MongoDBStorage(database); - } } }