-
Notifications
You must be signed in to change notification settings - Fork 3
Weather Slack bot implementation in .NET w/ location support #2
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from all commits
bd0d562
eb6b83a
6ca913e
8860a00
80f5cfa
b592269
1750f20
3a621a6
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,2 +1,37 @@ | ||
| /node_modules | ||
|
|
||
| #Ignore thumbnails created by Windows | ||
| Thumbs.db | ||
| #Ignore files built by Visual Studio | ||
| *.obj | ||
| *.exe | ||
| *.pdb | ||
| *.user | ||
| *.aps | ||
| *.pch | ||
| *.vspscc | ||
| *_i.c | ||
| *_p.c | ||
| *.ncb | ||
| *.suo | ||
| *.tlb | ||
| *.tlh | ||
| *.bak | ||
| *.cache | ||
| *.ilk | ||
| *.log | ||
| [Bb]in | ||
| [Dd]ebug*/ | ||
| *.lib | ||
| *.sbr | ||
| obj/ | ||
| [Rr]elease*/ | ||
| _ReSharper*/ | ||
| [Tt]est[Rr]esult* | ||
| .vs/ | ||
| #Nuget packages folder | ||
| packages/ | ||
|
|
||
|
|
||
| *.config | ||
| SlackBotPrototype/SlackBotPrototype/App.config |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,32 @@ | ||
|
|
||
| #Ignore thumbnails created by Windows | ||
| Thumbs.db | ||
| #Ignore files built by Visual Studio | ||
| *.obj | ||
| *.exe | ||
| *.pdb | ||
| *.user | ||
| *.aps | ||
| *.pch | ||
| *.vspscc | ||
| *_i.c | ||
| *_p.c | ||
| *.ncb | ||
| *.suo | ||
| *.tlb | ||
| *.tlh | ||
| *.bak | ||
| *.cache | ||
| *.ilk | ||
| *.log | ||
| [Bb]in | ||
| [Dd]ebug*/ | ||
| *.lib | ||
| *.sbr | ||
| obj/ | ||
| [Rr]elease*/ | ||
| _ReSharper*/ | ||
| [Tt]est[Rr]esult* | ||
| .vs/ | ||
| #Nuget packages folder | ||
| packages/ |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,72 @@ | ||
| <?xml version="1.0" encoding="utf-8"?> | ||
| <Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> | ||
| <Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" /> | ||
| <PropertyGroup> | ||
| <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration> | ||
| <Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform> | ||
| <ProjectGuid>{BEAB33E8-9F1E-4D39-B741-2069E95834DD}</ProjectGuid> | ||
| <OutputType>Library</OutputType> | ||
| <AppDesignerFolder>Properties</AppDesignerFolder> | ||
| <RootNamespace>API.Tests</RootNamespace> | ||
| <AssemblyName>API.Tests</AssemblyName> | ||
| <TargetFrameworkVersion>v4.5.2</TargetFrameworkVersion> | ||
| <FileAlignment>512</FileAlignment> | ||
| </PropertyGroup> | ||
| <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' "> | ||
| <DebugSymbols>true</DebugSymbols> | ||
| <DebugType>full</DebugType> | ||
| <Optimize>false</Optimize> | ||
| <OutputPath>bin\Debug\</OutputPath> | ||
| <DefineConstants>DEBUG;TRACE</DefineConstants> | ||
| <ErrorReport>prompt</ErrorReport> | ||
| <WarningLevel>4</WarningLevel> | ||
| </PropertyGroup> | ||
| <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' "> | ||
| <DebugType>pdbonly</DebugType> | ||
| <Optimize>true</Optimize> | ||
| <OutputPath>bin\Release\</OutputPath> | ||
| <DefineConstants>TRACE</DefineConstants> | ||
| <ErrorReport>prompt</ErrorReport> | ||
| <WarningLevel>4</WarningLevel> | ||
| </PropertyGroup> | ||
| <ItemGroup> | ||
| <Reference Include="Castle.Core, Version=4.1.1.0, Culture=neutral, PublicKeyToken=407dd0808d44fbdc, processorArchitecture=MSIL"> | ||
| <HintPath>..\packages\Castle.Core.4.1.1\lib\net45\Castle.Core.dll</HintPath> | ||
| </Reference> | ||
| <Reference Include="Moq, Version=4.7.99.0, Culture=neutral, PublicKeyToken=69f491c39445e920, processorArchitecture=MSIL"> | ||
| <HintPath>..\packages\Moq.4.7.99\lib\net45\Moq.dll</HintPath> | ||
| </Reference> | ||
| <Reference Include="nunit.framework, Version=3.7.1.0, Culture=neutral, PublicKeyToken=2638cd05610744eb, processorArchitecture=MSIL"> | ||
| <HintPath>..\packages\NUnit.3.7.1\lib\net45\nunit.framework.dll</HintPath> | ||
| </Reference> | ||
| <Reference Include="System" /> | ||
| <Reference Include="System.Core" /> | ||
| <Reference Include="System.Xml.Linq" /> | ||
| <Reference Include="System.Data.DataSetExtensions" /> | ||
| <Reference Include="Microsoft.CSharp" /> | ||
| <Reference Include="System.Data" /> | ||
| <Reference Include="System.Net.Http" /> | ||
| <Reference Include="System.Xml" /> | ||
| </ItemGroup> | ||
| <ItemGroup> | ||
| <Compile Include="CommandTests.cs" /> | ||
| <Compile Include="DarkSkyTests.cs" /> | ||
| <Compile Include="EmbeddedResourceTestBase.cs" /> | ||
| <Compile Include="LocationServiceTests.cs" /> | ||
| <Compile Include="MessageParserTests.cs" /> | ||
| <Compile Include="Properties\AssemblyInfo.cs" /> | ||
| </ItemGroup> | ||
| <ItemGroup> | ||
| <None Include="packages.config"> | ||
| <SubType>Designer</SubType> | ||
| </None> | ||
| <EmbeddedResource Include="TestData\dc-daily.json" /> | ||
| </ItemGroup> | ||
| <ItemGroup> | ||
| <ProjectReference Include="..\API\API.csproj"> | ||
| <Project>{A234D7B8-EAB9-497F-84E7-354DC066BCB5}</Project> | ||
| <Name>API</Name> | ||
| </ProjectReference> | ||
| </ItemGroup> | ||
| <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" /> | ||
| </Project> |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,122 @@ | ||
| using System; | ||
| using System.Collections.Generic; | ||
| using System.Linq; | ||
| using System.Text; | ||
| using System.Threading.Tasks; | ||
| using Moq; | ||
| using NUnit.Framework; | ||
|
|
||
| namespace API.Tests | ||
| { | ||
|
|
||
| [TestFixture] | ||
| public class CommandTests : EmbeddedResourceTestBase | ||
| { | ||
| private static readonly Location TestLocation = | ||
| new Location {Latitude = 38.889931, Longitude = -77.009003, DisplayName = "Washington, DC"}; | ||
|
|
||
| private IWeatherProvider _weatherProvider; | ||
| private IRepository _repository; | ||
|
|
||
| [OneTimeSetUp] | ||
| public void Init() | ||
| { | ||
| var mockHttp = new Mock<IHttpRequestClient>(); | ||
|
|
||
| var dailyForecastJson = GetFromResource("dc-daily.json"); | ||
| mockHttp.Setup(m => m.GetContent(It.IsAny<string>())).Returns(dailyForecastJson); | ||
| _weatherProvider = new DarkSkyWeatherProvider(ConfigConstants.DarkSkyApiSecret, mockHttp.Object); | ||
|
|
||
| var mockRepo = new Mock<IRepository>(); | ||
| mockRepo.Setup(m => m.GetLocation("San Francisco", "CA")) | ||
| .Returns(new Location() { City = "San Francisco", State = "CA", DisplayName = "San Francisco, CA" }); | ||
|
|
||
| _repository = mockRepo.Object; | ||
| } | ||
|
|
||
| [Test] | ||
| public void TestLocationInvalidWeatherCommand() | ||
| { | ||
|
|
||
| var location = new Location() | ||
| { | ||
| Latitude = 0, | ||
| Longitude = 0 | ||
| }; | ||
|
|
||
| var when = DateTime.Parse("2017/07/01"); | ||
|
|
||
| var weatherCommand = new WeatherNowCommand(_weatherProvider, location, when); | ||
| var msg = weatherCommand.Invoke(); | ||
|
|
||
| // SHORTCUT: A bit of shortcut here, normally we'd define a | ||
| // some sort of reponse code enum and test based on the response code. | ||
| // Since we don't return a result code right now (requires more boilerplate), just test based on expected string. | ||
| Assert.IsTrue(msg.Contains("No location specified")); | ||
| } | ||
|
|
||
| [Test] | ||
| public void TestValidLocationWeatherCommand() | ||
| { | ||
| var when = DateTime.Parse("2017/07/01"); | ||
|
|
||
| var weatherCommand = new WeatherNowCommand(_weatherProvider, TestLocation, when); | ||
| var msg = weatherCommand.Invoke(); | ||
|
|
||
| // we know that we always include the city in weather results, this would have | ||
| // to be updated as things change | ||
| Assert.IsTrue(msg.Contains("Washington, DC")); | ||
| } | ||
|
|
||
| [Test] | ||
| public void TestSetLocationCommand() | ||
| { | ||
| var location = new Location(); | ||
|
|
||
| var setLocationCommand = new SetUserLocationCommand(_repository, "Test", "123", location); | ||
| var msg = setLocationCommand.Invoke(); | ||
|
|
||
| Assert.IsTrue(msg.Contains("Location invalid")); | ||
| } | ||
|
|
||
| [Test] | ||
| public void TestSetValidLocationCommand() | ||
| { | ||
| var setLocationCommand = new SetUserLocationCommand(_repository, "Test", "123", TestLocation); | ||
|
|
||
| // Don't think there's much of a point to testing the repository inserts since | ||
| // without setting up an integration test with SQLite | ||
| var msg = setLocationCommand.Invoke(); | ||
|
|
||
| Assert.IsTrue(msg.Contains("Set location")); | ||
| } | ||
|
|
||
| [Test] | ||
| public void TestSetNullLocation() | ||
| { | ||
| var setLocationCommand = new SetUserLocationCommand(_repository, "Test", "123", null); | ||
|
|
||
| // Don't think there's much of a point to testing the repository inserts since | ||
| // without setting up an integration test with SQLite | ||
| var msg = setLocationCommand.Invoke(); | ||
|
|
||
| Assert.IsTrue(msg.Contains("Location not found")); | ||
| } | ||
|
|
||
| [TestCase(ExpectedResult = "")] | ||
| public string TestNoopCommand() | ||
| { | ||
| var noop = new NoOpCommand(); | ||
| return noop.Invoke(); | ||
| } | ||
|
|
||
| [Test] | ||
| public void TestWhatToWearCommand() | ||
| { | ||
| var whatToWear = new WhatToWearCommand(_weatherProvider); | ||
| var msg = whatToWear.Invoke(); | ||
|
|
||
| Assert.IsTrue(msg == "Bring a jacket"); | ||
| } | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,54 @@ | ||
| using System; | ||
| using System.Collections.Generic; | ||
| using System.IO; | ||
| using System.Linq; | ||
| using System.Reflection; | ||
| using System.Text; | ||
| using System.Threading.Tasks; | ||
| using Moq; | ||
| using NUnit.Framework; | ||
|
|
||
| namespace API.Tests | ||
| { | ||
|
|
||
| /// <summary> | ||
| /// Not a unit test, normally would do this in separate REPL but | ||
| /// put here for demonstrative purposes | ||
| /// </summary> | ||
| [TestFixture] | ||
| public class TestDarkskyRequests : EmbeddedResourceTestBase | ||
| { | ||
| private Location _testLocation; | ||
| private IWeatherProvider _weatherProvider; | ||
|
|
||
| [OneTimeSetUp] | ||
| public void Init() | ||
| { | ||
| _testLocation = new Location() { Latitude = 38.889931, Longitude = -77.0369 }; | ||
|
|
||
| var mockHttp = new Mock<IHttpRequestClient>(); | ||
|
|
||
| var dcDailyForecastJson = GetFromResource("dc-daily.json"); | ||
| mockHttp.Setup(m => m.GetContent(It.IsRegex("-77.0369"))).Returns(dcDailyForecastJson); | ||
| _weatherProvider = new DarkSkyWeatherProvider(ConfigConstants.DarkSkyApiSecret, mockHttp.Object); | ||
| } | ||
|
|
||
| [Test] | ||
| public void TestGetDailyForecast() | ||
| { | ||
| var forecast = _weatherProvider.GetForecast(_testLocation, DateTime.Now); | ||
| Assert.IsNotNull(forecast); | ||
| } | ||
|
|
||
| [Test] | ||
| public void TestSummary() | ||
| { | ||
| var forecast = _weatherProvider.GetForecast(_testLocation, DateTime.Now); | ||
| Assert.IsTrue(forecast == "Partly cloudy starting in the evening."); | ||
| } | ||
|
|
||
| // SHORTCUT: Skip testing for time based weather requests, we'd need to mock out | ||
| // the requests which doesn't really help testing the temporal component of the weather | ||
| // requests as those are really mainly handled by DarkSky | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,36 @@ | ||
| using System; | ||
| using System.Collections.Generic; | ||
| using System.IO; | ||
| using System.Linq; | ||
| using System.Reflection; | ||
| using System.Text; | ||
| using System.Threading.Tasks; | ||
| using NUnit.Framework; | ||
|
|
||
| namespace API.Tests | ||
| { | ||
| public class EmbeddedResourceTestBase | ||
| { | ||
|
|
||
| protected string GetFromResource(string fileName) | ||
| { | ||
| if (fileName == null) | ||
| throw new ArgumentNullException(nameof(fileName)); | ||
|
|
||
| var assembly = Assembly.GetExecutingAssembly(); | ||
| var resourceName = assembly.GetManifestResourceNames().First(x => x.Contains(fileName)); | ||
|
|
||
| using (var file = assembly.GetManifestResourceStream(resourceName)) | ||
| { | ||
| if (file == null) | ||
| Assert.Fail(); | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. i would
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Good intuition, |
||
|
|
||
| using (var sr = new StreamReader(file)) | ||
| { | ||
| var json = sr.ReadToEnd(); | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. nit: i don't think the thing its parsing has to be json. i would just do
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 👍 Good insight. |
||
| return json; | ||
| } | ||
| } | ||
| } | ||
| } | ||
| } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
since this doesn't use any instance variables or configure any other aspect of the class, should we look at just having it be either a mixin or a utility function? what are the downsides or upsides of using a class to do this?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, both make sense in this case for the reason you specified as alternatives for code sharing. Some considerations
protected, this limits access to our function more than a public utility method would. So caller's know that if they want to use this method, they have to inherit which means that the subclass has a relationship with the base class and it's functionality.