diff --git a/.github/workflows/dotnet.yml b/.github/workflows/dotnet.yml index ecae297..ac0840c 100644 --- a/.github/workflows/dotnet.yml +++ b/.github/workflows/dotnet.yml @@ -5,6 +5,7 @@ on: branches: [ master ] pull_request: branches: [ master ] + workflow_dispatch: jobs: build: @@ -22,4 +23,22 @@ jobs: - name: Build run: dotnet build --no-restore - name: Test - run: dotnet test --no-build --verbosity normal + run: dotnet test --no-build --verbosity normal --collect:"XPlat Code Coverage" --results-directory ./coverage + - name: Code Coverage Report + uses: irongut/CodeCoverageSummary@v1.3.0 + with: + filename: coverage/**/coverage.cobertura.xml + badge: true + fail_below_min: false + format: markdown + hide_branch_rate: false + hide_complexity: true + indicators: true + output: both + thresholds: '60 80' + - name: Add Coverage PR Comment + uses: marocchino/sticky-pull-request-comment@v2 + if: github.event_name == 'pull_request' + with: + recreate: true + path: code-coverage-results.md \ No newline at end of file diff --git a/BaseballSharp.sln b/BaseballSharp.sln index a0dcc65..ac49907 100644 --- a/BaseballSharp.sln +++ b/BaseballSharp.sln @@ -15,7 +15,9 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BaseballSharpCli", "samples EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BaseballSharp", "src\BaseballSharp.csproj", "{B188B5BF-0708-4F12-BD6A-3A0664BE9172}" EndProject -Global +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BaseballSharp.Test", "test\BaseballSharp.Test\BaseballSharp.Test.csproj", "{DB0661A1-F7C0-4D43-8BE3-DF66BF9FAF4A}" +EndProject +Global5206EC59-0954-4355-B48B-B014E0E79032 GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU Release|Any CPU = Release|Any CPU @@ -29,6 +31,10 @@ Global {B188B5BF-0708-4F12-BD6A-3A0664BE9172}.Debug|Any CPU.Build.0 = Debug|Any CPU {B188B5BF-0708-4F12-BD6A-3A0664BE9172}.Release|Any CPU.ActiveCfg = Release|Any CPU {B188B5BF-0708-4F12-BD6A-3A0664BE9172}.Release|Any CPU.Build.0 = Release|Any CPU + {DB0661A1-F7C0-4D43-8BE3-DF66BF9FAF4A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {DB0661A1-F7C0-4D43-8BE3-DF66BF9FAF4A}.Debug|Any CPU.Build.0 = Debug|Any CPU + {DB0661A1-F7C0-4D43-8BE3-DF66BF9FAF4A}.Release|Any CPU.ActiveCfg = Release|Any CPU + {DB0661A1-F7C0-4D43-8BE3-DF66BF9FAF4A}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/src/MLBClient.cs b/src/MLBClient.cs index 2a406b4..497a1b7 100644 --- a/src/MLBClient.cs +++ b/src/MLBClient.cs @@ -8,10 +8,13 @@ using System; using System.Collections.Generic; using System.Net.Http; +using System.Runtime.CompilerServices; using System.Text.Json; using System.Threading.Tasks; using Team = BaseballSharp.DTO.Teams.Team; +[assembly: InternalsVisibleTo("BaseballSharp.Test")] + namespace BaseballSharp { /// @@ -19,10 +22,18 @@ namespace BaseballSharp /// public class MLBClient : IMLBClient { - private static HttpClient _httpClient = new HttpClient(); + private HttpClient _httpClient = new HttpClient(); private static readonly string _baseUrl = "https://statsapi.mlb.com/api/v1"; private static readonly short _outsInCompletedInning = 3; + internal HttpClient HttpClient + { + set + { + _httpClient = value; + } + } + private async Task GetResponseAsync(string endpoint) { @@ -52,6 +63,7 @@ public async Task> GetScheduleAsync(DateTime date) gameID = game.gamePk, AwayTeam = game.teams?.away?.team?.name, HomeTeam = game.teams?.home?.team?.name, + Ballpark = game.venue?.name, ScheduledInnings = game.scheduledInnings, StatusCode = Schedule.GetStatusCode(game.status?.statusCode) }); @@ -111,7 +123,8 @@ public async Task> GetPitchingReportsAsync(DateTime { teamsList.Add(new Models.Team() { - Name = team.name, + FullName = team.name, + Name = team.teamName, Location = team.locationName, Id = team.id, LeagueId = team.league?.id, diff --git a/test/BaseballSharp.Test/BaseballSharp.Test.csproj b/test/BaseballSharp.Test/BaseballSharp.Test.csproj new file mode 100644 index 0000000..d5541df --- /dev/null +++ b/test/BaseballSharp.Test/BaseballSharp.Test.csproj @@ -0,0 +1,36 @@ + + + + net8.0 + enable + enable + + false + true + + + + + + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + + diff --git a/test/BaseballSharp.Test/GlobalUsings.cs b/test/BaseballSharp.Test/GlobalUsings.cs new file mode 100644 index 0000000..8c927eb --- /dev/null +++ b/test/BaseballSharp.Test/GlobalUsings.cs @@ -0,0 +1 @@ +global using Xunit; \ No newline at end of file diff --git a/test/BaseballSharp.Test/MLBClientShould.cs b/test/BaseballSharp.Test/MLBClientShould.cs new file mode 100644 index 0000000..db3cfe5 --- /dev/null +++ b/test/BaseballSharp.Test/MLBClientShould.cs @@ -0,0 +1,94 @@ + +using System.Net; +using Moq; +using Moq.Protected; + +namespace BaseballSharp.Test +{ + public class MLBClientShould + { + private HttpClient BuildMockedHttpClient(string returnContent) + { + var handlerMock = new Mock(MockBehavior.Strict); + + handlerMock + .Protected() + // Setup the PROTECTED method to mock + .Setup>( + "SendAsync", + ItExpr.IsAny(), + ItExpr.IsAny() + ) + // Prepare the expected response of the mocked HttpClient + .ReturnsAsync(new HttpResponseMessage() + { + StatusCode = HttpStatusCode.OK, + Content = new StringContent(returnContent), + }) + .Verifiable(); + + return new HttpClient(handlerMock.Object); + } + + [Fact] + public async void ReturnAScheduleGivenAValidDate() + { + var sut = new MLBClient + { + HttpClient = BuildMockedHttpClient(File.ReadAllText("Schedule.json")), + }; + + var game = (await sut.GetScheduleAsync(DateTime.Now)).ToList().Single(); + + Assert.Equal("Toronto Blue Jays", game.AwayTeam); + Assert.Equal("Comerica Park", game.Ballpark); + Assert.Equal(746470, game.gameID); + Assert.Equal("Detroit Tigers", game.HomeTeam); + Assert.Equal(9, game.ScheduledInnings); + Assert.Equal(Enums.GameStatus.PreGame, game.StatusCode); + } + + [Fact] + public async void ReturnAValidPitchingReportGivenAValidDate() + { + var sut = new MLBClient + { + HttpClient = BuildMockedHttpClient(File.ReadAllText("PitchingReports.json")), + }; + + var pitchingReport = (await sut.GetPitchingReportsAsync(DateTime.Now)).ToList().Single(); + + Assert.Equal(607192, pitchingReport.AwayProbablePitcherId); + Assert.Equal("Tyler Glasnow", pitchingReport.AwayProbablePitcherName); + Assert.Equal("", pitchingReport.AwayProbablePitcherNotes); + Assert.Equal("Los Angeles Dodgers", pitchingReport.AwayTeam); + Assert.Equal(656731, pitchingReport.HomeProbablePitcherId); + Assert.Equal("Tylor Megill", pitchingReport.HomeProbablePitcherName); + Assert.Equal("", pitchingReport.HomeProbablePitcherNotes); + Assert.Equal("New York Mets", pitchingReport.HomeTeam); + } + + [Fact] + public async void ReturnValidTeamData() + { + var sut = new MLBClient + { + HttpClient = BuildMockedHttpClient(File.ReadAllText("TeamData.json")), + }; + + var teamData = (await sut.GetTeamDataAsync()).ToList().Single(); + + Assert.Equal("OAK", teamData.Abbreviation); + Assert.Equal(200, teamData.DivisionId); + Assert.Equal("American League West", teamData.DivisionName); + Assert.Equal("Oakland Athletics", teamData.FullName); + Assert.Equal(133, teamData.Id); + Assert.Equal(103, teamData.LeagueId); + Assert.Equal("American League", teamData.LeagueName); + Assert.Equal("Oakland", teamData.Location); + Assert.Equal("Athletics", teamData.Name); + Assert.Equal(10, teamData.VenueId); + Assert.Equal("Oakland Coliseum", teamData.VenueName); + } + } +} diff --git a/test/BaseballSharp.Test/PitchingReports.json b/test/BaseballSharp.Test/PitchingReports.json new file mode 100644 index 0000000..fd3539a --- /dev/null +++ b/test/BaseballSharp.Test/PitchingReports.json @@ -0,0 +1,38 @@ +{ + "dates": [ + { + "date": "2024-05-28", + "games": [ + { + "gamePk": 745818, + "gameDate": "2024-05-28T20:10:00Z", + "status": { + "abstractGameState": "Final" + }, + "teams": { + "away": { + "team": { + "id": 119, + "name": "Los Angeles Dodgers" + }, + "probablePitcher": { + "id": 607192, + "fullName": "Tyler Glasnow" + } + }, + "home": { + "team": { + "id": 121, + "name": "New York Mets" + }, + "probablePitcher": { + "id": 656731, + "fullName": "Tylor Megill" + } + } + } + } + ] + } + ] +} \ No newline at end of file diff --git a/test/BaseballSharp.Test/Schedule.json b/test/BaseballSharp.Test/Schedule.json new file mode 100644 index 0000000..8078ec1 --- /dev/null +++ b/test/BaseballSharp.Test/Schedule.json @@ -0,0 +1,93 @@ +{ + "copyright": "Copyright 2024 MLB Advanced Media, L.P. Use of any content on this page acknowledges agreement to the terms posted here http://gdx.mlb.com/components/copyright.txt", + "totalItems": 15, + "totalEvents": 0, + "totalGames": 15, + "totalGamesInProgress": 0, + "dates": [ + { + "date": "2024-05-25", + "totalItems": 15, + "totalEvents": 0, + "totalGames": 15, + "totalGamesInProgress": 0, + "games": [ + { + "gamePk": 746470, + "gameGuid": "02f1258c-fe62-41ae-ab53-374dec7bca3d", + "link": "/api/v1.1/game/746470/feed/live", + "gameType": "R", + "season": "2024", + "gameDate": "2024-05-25T17:10:00Z", + "officialDate": "2024-05-25", + "status": { + "abstractGameState": "Preview", + "codedGameState": "P", + "detailedState": "Pre-Game", + "statusCode": "P", + "startTimeTBD": false, + "abstractGameCode": "P" + }, + "teams": { + "away": { + "leagueRecord": { + "wins": 23, + "losses": 27, + "pct": ".460" + }, + "score": 0, + "team": { + "id": 141, + "name": "Toronto Blue Jays", + "link": "/api/v1/teams/141" + }, + "splitSquad": false, + "seriesNumber": 17 + }, + "home": { + "leagueRecord": { + "wins": 24, + "losses": 27, + "pct": ".471" + }, + "score": 0, + "team": { + "id": 116, + "name": "Detroit Tigers", + "link": "/api/v1/teams/116" + }, + "splitSquad": false, + "seriesNumber": 17 + } + }, + "venue": { + "id": 2394, + "name": "Comerica Park", + "link": "/api/v1/venues/2394" + }, + "content": { + "link": "/api/v1/game/746470/content" + }, + "gameNumber": 1, + "publicFacing": true, + "doubleHeader": "N", + "gamedayType": "P", + "tiebreaker": "N", + "calendarEventID": "14-746470-2024-05-25", + "seasonDisplay": "2024", + "dayNight": "day", + "scheduledInnings": 9, + "reverseHomeAwayStatus": false, + "inningBreakLength": 120, + "gamesInSeries": 4, + "seriesGameNumber": 3, + "seriesDescription": "Regular Season", + "recordSource": "S", + "ifNecessary": "N", + "ifNecessaryDescription": "Normal Game" + } + ], + "events": [] + } + ] +} \ No newline at end of file diff --git a/test/BaseballSharp.Test/TeamData.json b/test/BaseballSharp.Test/TeamData.json new file mode 100644 index 0000000..5deaf1c --- /dev/null +++ b/test/BaseballSharp.Test/TeamData.json @@ -0,0 +1,52 @@ +{ + "copyright": "Copyright 2024 MLB Advanced Media, L.P. Use of any content on this page acknowledges agreement to the terms posted here http://gdx.mlb.com/components/copyright.txt", + "teams": [ + { + "springLeague": { + "id": 114, + "name": "Cactus League", + "link": "/api/v1/league/114", + "abbreviation": "CL" + }, + "allStarStatus": "N", + "id": 133, + "name": "Oakland Athletics", + "link": "/api/v1/teams/133", + "season": 2024, + "venue": { + "id": 10, + "name": "Oakland Coliseum", + "link": "/api/v1/venues/10" + }, + "springVenue": { + "id": 2507, + "link": "/api/v1/venues/2507" + }, + "teamCode": "oak", + "fileCode": "oak", + "abbreviation": "OAK", + "teamName": "Athletics", + "locationName": "Oakland", + "firstYearOfPlay": "1901", + "league": { + "id": 103, + "name": "American League", + "link": "/api/v1/league/103" + }, + "division": { + "id": 200, + "name": "American League West", + "link": "/api/v1/divisions/200" + }, + "sport": { + "id": 1, + "link": "/api/v1/sports/1", + "name": "Major League Baseball" + }, + "shortName": "Oakland", + "franchiseName": "Oakland", + "clubName": "Athletics", + "active": true + } + ] +} \ No newline at end of file