/*
* Copyright (c) 2025 Proton AG
*
* This file is part of ProtonVPN.
*
* ProtonVPN is free software:
you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* ProtonVPN is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with ProtonVPN. If not, see .
*/
using System.Net;
using FluentAssertions;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using ProtonVPN.Client.Logic.Announcements;
using ProtonVPN.Client.Logic.Announcements.Contracts;
using ProtonVPN.Client.Logic.Announcements.Contracts.Entities;
using ProtonVPN.Configurations.Contracts;
using ProtonVPN.Integration.Tests;
using RichardSzalay.MockHttp;
namespace ProtonVPN.Integration.Tests.Announcements;
[TestClass]
public class AnnouncementsProviderTest : AuthenticatedUserTests
{
private const string FULL_SCREEN_IMAGE_PATH = "https://protonvpn.com/full-screen-image.png";
private IAnnouncementsProvider? _announcementsProvider;
[TestInitialize]
public async Task InitializeAsync()
{
InitializeContainer();
_announcementsProvider = Resolve();
SetApiResponsesForAuth();
await MakeUserAuthAsync(CORRECT_PASSWORD);
}
[TestCleanup]
public override void Cleanup()
{
ClearImages();
foreach (Announcement announcement in _announcementsProvider!.GetAllActive())
{
_announcementsProvider!.Delete(announcement.Id);
}
base.Cleanup();
}
[TestMethod]
public async Task ItShouldStoreAnnouncementsToCacheAsync()
{
// Arrange
InitializeAnnouncementMock("AnnouncementsResponseMock");
// Act
await Resolve().UpdateAsync();
// Assert
AssertAnnouncement();
}
[TestMethod]
public async Task ItShouldDisplayAnnouncementOnlyIfTheOfferFieldIsNotMissingAsync()
{
// Arrange
InitializeAnnouncementMock("MissingOfferAnnouncementsResponseMock");
// Act
await Resolve().UpdateAsync();
// Assert
AssertAnnouncement();
}
[TestMethod]
public async Task ItShouldNotDisplayAnnouncementIfTheDataIsPartiallyCorruptAsync()
{
// Arrange
InitializeAnnouncementMock("InvalidDataAnnouncementsResponseMock");
// Act
await Resolve().UpdateAsync();
// Assert
AssertAnnouncement();
}
[TestMethod]
public async Task ItShouldDownloadFullScreenImageAsync()
{
// Arrange
InitializeFullScreenImageMock();
// Act
await Resolve().UpdateAsync();
// Assert
IReadOnlyList announcements = Resolve().GetAllActive();
File.Exists(announcements[0].Panel?.FullScreenImage?.Image?.LocalPath).Should().BeTrue();
}
[TestMethod]
public async Task ItShouldNotDownloadFullScreenImageIfItAlreadyExistsAsync()
{
// Arrange
MockedRequest request = InitializeFullScreenImageMock();
// Act
await Resolve().UpdateAsync();
await Resolve().UpdateAsync();
// Assert
MessageHandler!.GetMatchCount(request).Should().Be(1);
}
[TestMethod]
public async Task ItShouldIgnoreAnnouncementIfFailedToDownloadFullScreenImageAsync()
{
// Arrange
MessageHandler!.When(HttpMethod.Get, FULL_SCREEN_IMAGE_PATH).Throw(new HttpRequestException());
InitializeAnnouncementMock("FullScreenImageAnnouncementResponseMock");
// Act
await Resolve().UpdateAsync();
// Assert
Resolve().GetAllActive().Count.Should().Be(0);
}
[TestMethod]
public async Task ItShouldIgnoreUnsupportedFullscreenImageFormatsAsync()
{
// Arrange
InitializeAnnouncementMock("UnsupportedFullScreenImageMock");
// Act
await Resolve().UpdateAsync();
// Assert
Resolve().GetAllActive().Count.Should().Be(0);
}
private MockedRequest InitializeFullScreenImageMock()
{
MockedRequest mockedRequest = MessageHandler!.When(HttpMethod.Get, FULL_SCREEN_IMAGE_PATH)
.Respond(_ => new HttpResponseMessage
{
StatusCode = HttpStatusCode.OK,
Content = new StringContent(string.Empty)
{
Headers =
{
ContentType = new System.Net.Http.Headers.MediaTypeHeaderValue("image/png")
}
}
});
InitializeAnnouncementMock("FullScreenImageAnnouncementResponseMock");
return mockedRequest;
}
private void InitializeAnnouncementMock(string nameOfMock)
{
MessageHandler!.When(HttpMethod.Get, "/core/v4/notifications").Respond(_ => new HttpResponseMessage
{
StatusCode = HttpStatusCode.OK,
Content = new StringContent(GetJsonMock(nameOfMock))
});
// TODO: no loner needed?
//Resolve().FeaturePollNotificationApiEnabled = true;
}
private void ClearImages()
{
string imageCacheFolder = Resolve().ImageCacheFolder;
if (Directory.Exists(imageCacheFolder))
{
Directory.Delete(imageCacheFolder, true);
}
}
private void AssertAnnouncement()
{
IReadOnlyList announcements = Resolve().GetAllActive();
announcements.Count.Should().Be(1);
Announcement announcement = announcements[0];
announcement.Seen.Should().BeFalse();
announcement.Label.Should().Be("VPN Summer Survey");
// TODO: check if Pill should still exist
//announcement.Panel.Pill.Should().Be("Win a $50 gift card!");
announcement.Panel?.Title.Should().Be("VPN Summer Survey");
announcement.Panel?.Button?.Text.Should().Be("Take the survey");
}
}