diff --git a/README.md b/README.md index fc190cc..1810509 100644 --- a/README.md +++ b/README.md @@ -44,6 +44,17 @@ if (isAuthenticated) You must call the `Authenticate()` method with a valid `username`, `password` and `schoolId`. +You can use the utility method `FindSchools()` to find out the `schoolId`. + +```csharp +var schoolName = "Kingsdale"; +var school = (await authService.FindSchools(schoolName)).FirstOrDefault(); +if(school != null) +{ + var schoolId = school.Id; +} +``` + ## Disclaimer This library is not supported by ShowMyHomework.co.uk and is just the result of his author understanding about how the ShowMyHomework API works, from observing its interactions with the public website. There is no public documentation about the API and it can change any time diff --git a/samples/Ovicus.ShowMyHomework.Sample/Program.cs b/samples/Ovicus.ShowMyHomework.Sample/Program.cs index 168d4fd..e252c81 100644 --- a/samples/Ovicus.ShowMyHomework.Sample/Program.cs +++ b/samples/Ovicus.ShowMyHomework.Sample/Program.cs @@ -1,5 +1,6 @@ using Ovicus.ShowMyHomework.Auth; using System; +using System.Linq; using System.Threading.Tasks; namespace Ovicus.ShowMyHomework.Sample @@ -16,21 +17,31 @@ static async Task Main(string[] args) { var authService = new AuthenticationService(); - Console.Write("Enter School Id: "); - var schoolId = Console.ReadLine(); + Console.Write("Enter School Name: "); + var schoolName = Console.ReadLine(); - Console.Write("Enter Username: "); - var username = Console.ReadLine(); + var school = (await authService.FindSchools(schoolName)).FirstOrDefault(); - Console.Write("Enter Password: "); - var password = Console.ReadLine(); + if (school != null) + { + + Console.Write("Enter Username: "); + var username = Console.ReadLine(); - bool isAuthenticated = await authService.Authenticate(username, password, schoolId); + Console.Write("Enter Password: "); + var password = Console.ReadLine(); - if (isAuthenticated) + bool isAuthenticated = await authService.Authenticate(username, password, school.Id); + + if (isAuthenticated) + { + accessToken = await authService.GetAccessToken(); + await PrintTodos(accessToken); + } + } + else { - accessToken = await authService.GetAccessToken(); - await PrintTodos(accessToken); + Console.WriteLine($"School not found: {schoolName}"); } } else diff --git a/src/Ovicus.ShowMyHomework.Auth/AuthenticationService.cs b/src/Ovicus.ShowMyHomework.Auth/AuthenticationService.cs index bafed74..9b10d27 100644 --- a/src/Ovicus.ShowMyHomework.Auth/AuthenticationService.cs +++ b/src/Ovicus.ShowMyHomework.Auth/AuthenticationService.cs @@ -1,7 +1,9 @@ using Newtonsoft.Json; +using Newtonsoft.Json.Linq; using Ovicus.ShowMyHomework.Auth.Extensions; using System; using System.Collections.Generic; +using System.Linq; using System.Net.Http; using System.Net.Http.Headers; using System.Threading.Tasks; @@ -11,7 +13,7 @@ namespace Ovicus.ShowMyHomework.Auth public interface IAuthenticationService { Task GetAccessToken(); - Task Authenticate(string username, string password, string schoolId); + Task Authenticate(string username, string password, long schoolId); } public class AuthenticationService : IAuthenticationService @@ -42,7 +44,7 @@ public AuthenticationService(string endpointBase, HttpClient client) { } - public async Task Authenticate(string username, string password, string schoolId) + public async Task Authenticate(string username, string password, long schoolId) { // Get new token with grant_type=password var requestUrl = $"/oauth/token?client_id={OAuthClientId}&client_secret={OAuthClientSecret}"; @@ -50,7 +52,7 @@ public async Task Authenticate(string username, string password, string sc var keyValues = new List>(); keyValues.Add(new KeyValuePair("grant_type", "password")); - keyValues.Add(new KeyValuePair("school_id", schoolId)); + keyValues.Add(new KeyValuePair("school_id", schoolId.ToString())); keyValues.Add(new KeyValuePair("username", username)); keyValues.Add(new KeyValuePair("password", password)); @@ -100,5 +102,15 @@ private async Task RefreshToken() return _currentToken; } + + public async Task> FindSchools(string filterName) + { + var url = $"/api/public/school_search?filter={filterName}&limit=20"; + var response = await _client.GetAsync(url); + var json = await response.EnsureSuccessStatusCode().Content.ReadAsStringAsync(); + JObject root = JObject.Parse(json); + var schools = root.SelectToken("schools").ToObject(); + return schools.Where(s => s.IsActive); + } } } \ No newline at end of file diff --git a/src/Ovicus.ShowMyHomework.Auth/School.cs b/src/Ovicus.ShowMyHomework.Auth/School.cs new file mode 100644 index 0000000..bcb258b --- /dev/null +++ b/src/Ovicus.ShowMyHomework.Auth/School.cs @@ -0,0 +1,24 @@ +using Newtonsoft.Json; +using System; +using System.Collections.Generic; +using System.Text; + +namespace Ovicus.ShowMyHomework.Auth +{ + public class School + { + public long Id { get; set; } + + public string Name { get; set; } + + public string Address { get; set; } + + public string Town { get; set; } + + [JsonProperty("post_code")] + public string PostCode { get; set; } + + [JsonProperty("is_active")] + public bool IsActive { get; set; } + } +} diff --git a/tests/Ovicus.ShowMyHomework.Tests/AuthenticationServiceTests.cs b/tests/Ovicus.ShowMyHomework.Tests/AuthenticationServiceTests.cs index f7a28a3..bdb51c8 100644 --- a/tests/Ovicus.ShowMyHomework.Tests/AuthenticationServiceTests.cs +++ b/tests/Ovicus.ShowMyHomework.Tests/AuthenticationServiceTests.cs @@ -14,7 +14,94 @@ namespace Ovicus.ShowMyHomework.Tests public class AuthenticationServiceTests { private const string ApiBaseUrl = "https://api.showmyhomework.co.uk"; - + + [Fact] + public async Task When_FindSchoolsCalledWithValidFilter_Then_ActiveMatchingSchoolsShouldBeReturned() + { + // ARRANGE + string responseBody = "{" + + "\"schools\": [" + + "{" + + "\"id\": 1234," + + "\"name\": \"Kings Langley School\"," + + "\"address\": \"Love Lane, Hertfordshire\"," + + "\"town\": \"Kings Langley\"," + + "\"post_code\": \"WD4 9HN\"," + + "\"subdomain\": \"kingslangley\"," + + "\"is_active\": true," + + "}," + + "{" + + "\"id\": 1235," + + "\"name\": \"Kingsley Academy\"," + + "\"address\": \"Cecil Road\"," + + "\"town\": \"Hounslow\"," + + "\"post_code\": \"TW3 1AX\"," + + "\"subdomain\": \"kingsleyacademy\"," + + "\"is_active\": false," + + "}," + + "{ " + + "\"id\": 1236," + + "\"name\": \"Kingsdale Foundation School\"," + + "\"address\": \"Alleyn Park, Dulwich\"," + + "\"town\": \"London\"," + + "\"post_code\": \"SE21 8SQ\"," + + "\"subdomain\": \"kingsdale\"," + + "\"is_active\": true," + + "}]}"; + + Mock handlerMock = MockHttpMessageHandlerBuilder.Build(responseBody, HttpStatusCode.OK); + // use real http client with mocked handler here + HttpClient httpClient = new HttpClient(handlerMock.Object); + + var sut = new AuthenticationService(ApiBaseUrl, httpClient); + + // ACT + var result = await sut.FindSchools("Kings"); + + // ASSERT + result.Should().NotBeNull(); + result.Should().HaveCount(2); + result.Should().OnlyContain(s => s.IsActive); + + handlerMock.Protected().Verify( + "SendAsync", + Times.Exactly(1), // we expected a single external request + ItExpr.Is(req => + req.Method == HttpMethod.Get // we expected a POST request + ), + ItExpr.IsAny() + ); + } + + [Fact] + public async Task When_FindSchoolsCalledWithInvalidFilter_Then_ShouldReturnNoResults() + { + // ARRANGE + string responseBody = "{ \"schools\": [] }"; + + Mock handlerMock = MockHttpMessageHandlerBuilder.Build(responseBody, HttpStatusCode.OK); + // use real http client with mocked handler here + HttpClient httpClient = new HttpClient(handlerMock.Object); + + var sut = new AuthenticationService(ApiBaseUrl, httpClient); + + // ACT + var result = await sut.FindSchools("NonExistantSchoolName"); + + // ASSERT + result.Should().NotBeNull(); + result.Should().BeEmpty(); + + handlerMock.Protected().Verify( + "SendAsync", + Times.Exactly(1), // we expected a single external request + ItExpr.Is(req => + req.Method == HttpMethod.Get // we expected a POST request + ), + ItExpr.IsAny() + ); + } + [Fact] public async Task When_ValidCredentialsAreProvided_Then_AuthenticationSucceedAndAccessTokenCreated() { @@ -38,7 +125,7 @@ public async Task When_ValidCredentialsAreProvided_Then_AuthenticationSucceedAnd var sut = new AuthenticationService(ApiBaseUrl, httpClient); // ACT - var result = await sut.Authenticate("username", "$3cret", "1234"); + var result = await sut.Authenticate("username", "$3cret", 1234); var token = await sut.GetAccessToken(); // ASSERT @@ -69,7 +156,7 @@ public async Task When_InvalidCredentialsAreProvided_Then_AuthenticationFailsAnd var sut = new AuthenticationService(ApiBaseUrl, httpClient); // ACT - var result = await sut.Authenticate("username", "inv@lidPass", "1234"); + var result = await sut.Authenticate("username", "inv@lidPass", 1234); // ASSERT result.Should().BeFalse(); @@ -139,7 +226,7 @@ public async Task When_TokenIsExpired_Should_TryRefresh() // This will make a request to the authentication endpoint and retrieve the (expired) token // This method does not check the validity of the token, just simply trust // the authentication server will issue a valid token on successful authentication - var authResult = await sut.Authenticate("username", "$3cret", "1234"); + var authResult = await sut.Authenticate("username", "$3cret", 1234); // ARRANGE (2nd stage)