8000 fix(notifications): prevent Meta 63013 policy violation by sending no… by adrianwium · Pull Request #1393 · didx-xyz/yoma · GitHub
[go: up one dir, main page]
More Web Proxy on the site http://driver.im/
Skip to content

fix(notifications): prevent Meta 63013 policy violation by sending no… #1393

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

Merged
merged 3 commits into from
Apr 30, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ public Models.Lookups.DownloadScheduleStatus GetById(Guid id)

public List<Models.Lookups.DownloadScheduleStatus> List()
{
if (!_appSettings.CacheEnabledByCacheItemTypesAsEnum.HasFlag(Core.CacheItemType.Lookups))
if (!_appSettings.CacheEnabledByCacheItemTypesAsEnum.HasFlag(CacheItemType.Lookups))
return [.. _downloadScheduleStatusRepository.Query().OrderBy(o => o.Name)];

var result = _memoryCache.GetOrCreate(CacheHelper.GenerateKey<Models.Lookups.DownloadScheduleStatus>(), entry =>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ public Models.Lookups.Partner GetById(Guid id)

public List<Models.Lookups.Partner> List()
{
if (!_appSettings.CacheEnabledByCacheItemTypesAsEnum.HasFlag(Core.CacheItemType.Lookups))
if (!_appSettings.CacheEnabledByCacheItemTypesAsEnum.HasFlag(CacheItemType.Lookups))
return [.. _partnerRepository.Query().OrderBy(o => o.Name)];

var result = _memoryCache.GetOrCreate(CacheHelper.GenerateKey<Models.Lookups.Partner>(), entry =>
Expand Down
Original file line n 8000 umber Diff line number Diff line change
@@ -1,4 +1,3 @@
using CsvHelper.Configuration.Attributes;
using Microsoft.Extensions.Logging;
using Newtonsoft.Json;
using System.Text.RegularExpressions;
Expand Down Expand Up @@ -108,7 +107,7 @@ public async Task Send<T>(MessageType allowedMessageTypes, NotificationType noti

//ensure environment suffix
data.SubjectSuffix = _environmentProvider.Environment == Domain.Core.Environment.Production
? string.Empty
? " " //empty string results in "63013 This message send failed because it violates Channel provider's policy"
: $" ({_environmentProvider.Environment.ToDescription()})";

foreach (var recipient in recipients)
Expand Down Expand Up @@ -214,10 +213,15 @@ public async Task Send<T>(MessageType allowedMessageTypes, NotificationType noti
{
message = await MessageResource.FetchAsync(new FetchMessageOptions(response.Sid), _twilioClient);

if (message.Status == MessageResource.StatusEnum.Delivered || message.Status == MessageResource.StatusEnum.Read)
if (message.Status == MessageResource.StatusEnum.Failed || message.Status == MessageResource.StatusEnum.Undelivered)
{
lastTwilioFailure = $"{recipientId}: Twilio API error — {message.ErrorMessage ?? "Unknown error"} ({message.ErrorCode})";
break; //hard failure → stop polling
}
else if (message.Status == MessageResource.StatusEnum.Delivered || message.Status == MessageResource.StatusEnum.Read)
{
delivered = true;
break;
break; //success → stop polling
}
}
catch (Exception ex)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,60 +1,82 @@
using Microsoft.Extensions.DependencyInjection;
using Xunit;
using Yoma.Core.Domain.Core;
using Yoma.Core.Domain.Core.Extensions;
using Yoma.Core.Domain.Lookups.Interfaces;
using Yoma.Core.Domain.Notification;
using Yoma.Core.Domain.Notification.Interfaces;
using Yoma.Core.Domain.Notification.Models;
using Yoma.Core.Domain.Opportunity.Models;

namespace Yoma.Core.Test.Notification
{
public class WhatsAppNotificationTests : IClassFixture<CustomWebApplicationFactory>
{
#region Class Variables
private readonly INotificationDeliveryService _service;
private readonly IServiceScope _scope;
private readonly INotificationDeliveryService _notificationDeliveryService;
private readonly INotificationURLFactory _notificationURLFactory;
private readonly ICountryService _countryService;
#endregion

#region Constructor
public WhatsAppNotificationTests(CustomWebApplicationFactory factory)
{
using var scope = factory.Services.CreateScope();
_service = scope.ServiceProvider.GetRequiredService<INotificationDeliveryService>();
_scope = factory.Services.CreateScope();
var provider = _scope.ServiceProvider;

_notificationDeliveryService = provider.GetRequiredService<INotificationDeliveryService>();
_notificationURLFactory = provider.GetRequiredService<INotificationURLFactory>();
_countryService = provider.GetRequiredService<ICountryService>();
}
#endregion

#region Public Members
[Trait("Category", "WhatsApp")]
[Fact]
public async Task Send_OpportunityPublished_Should_Send_WhatsApp()
{
var opportunity = CreateTestOpportunity();
await SendOpportunityPublishedNotification_WhatsAppOnly(opportunity);
}

[Trait("Category", "WhatsApp")]
[Fact]
public async Task Send_OpportunityVerificationCompleted_Should_Send_WhatsApp()
{
var opportunity = CreateTestOpportunity("Verification Completed");
var opportunity = CreateTestMyOpportunity("Verification Completed");
await SendNotification_WhatsAppOnly(opportunity, NotificationType.Opportunity_Verification_Completed);
}

[Trait("Category", "WhatsApp")]
[Fact]
public async Task Send_OpportunityVerificationRejected_Should_Send_WhatsApp()
{
var opportunity = CreateTestOpportunity("Verification Rejected");
var opportunity = CreateTestMyOpportunity("Verification Rejected");
opportunity.CommentVerification = "ID document was blurry";
await SendNotification_WhatsAppOnly(opportunity, NotificationType.Opportunity_Verification_Rejected);
}

[Trait("Category", "WhatsApp")]
[Fact]
public async Task Send_OpportunityVerificationPending_Should_Send_WhatsApp()
{
var opportunity = CreateTestOpportunity("Verification Pending");
var opportunity = CreateTestMyOpportunity("Verification Pending");
await SendNotification_WhatsAppOnly(opportunity, NotificationType.Opportunity_Verification_Pending);
}

[Fact]
public void Dispose()
{
_scope.Dispose();
}
#endregion

#region Private Members
private Domain.MyOpportunity.Models.MyOpportunity CreateTestOpportunity(string title)
private static Domain.MyOpportunity.Models.MyOpportunity CreateTestMyOpportunity(string title)
{
return new Domain.MyOpportunity.Models.MyOpportunity
{
Username = "testuser",
UserPhoneNumber = "+27831234567",
UserPhoneNumberConfirmed = true,
UserEmail = "testuser@example.com",
UserEmailConfirmed = true,
UserDisplayName = "Test User",
OpportunityTitle = title,
DateStart = DateTime.UtcNow.AddDays(-7),
DateEnd = DateTime.UtcNow.AddDays(7),
Expand All @@ -66,40 +88,75 @@ private Domain.MyOpportunity.Models.MyOpportunity CreateTestOpportunity(string t
};
}

private async Task SendNotification_WhatsAppOnly(Domain.MyOpportunity.Models.MyOpportunity myOpportunity, NotificationType type)
private static List<NotificationRecipient> CreateTestRecipients() => [new()
{
var recipients = new List<NotificationRecipient>
Username = "testuser",
PhoneNumber = "+27831234567",
PhoneNumberConfirmed = true,
DisplayName = "Test User"
}];


private static Opportunity CreateTestOpportunity(string title = "Test Opportunity")
{
new()
{
Username = myOpportunity.Username,
PhoneNumber = myOpportunity.UserPhoneNumber,
PhoneNumberConfirmed = myOpportunity.UserPhoneNumberConfirmed,
DisplayName = myOpportunity.UserDisplayName
}
};
return new Opportunity
{
Id = Guid.NewGuid(),
Title = title,
DateStart = DateTimeOffset.UtcNow.AddDays(1),
DateEnd = DateTimeOffset.UtcNow.AddDays(10),
ZltoReward = 100,
YomaReward = 50,
OrganizationId = Guid.NewGuid()
};
}

private async Task SendNotification_WhatsAppOnly(Domain.MyOpportunity.Models.MyOpportunity myOpportunity, NotificationType type)
{
var data = new NotificationOpportunityVerification
{
YoIDURL = $"https://dummy.yoma.world/yoid/{type}",
VerificationURL = $"https://dummy.yoma.world/verify/{myOpportunity.OrganizationId}",
YoIDURL = _notificationURLFactory.OpportunityVerificationYoIDURL(type),
VerificationURL = _notificationURLFactory.OpportunityVerificationURL(type, myOpportunity.OrganizationId),
Opportunities = [
new NotificationOpportunityVerificationItem
{
Title = myOpportunity.OpportunityTitle,
DateStart = myOpportunity.DateStart,
DateEnd = myOpportunity.DateEnd,
Comment = myOpportunity.CommentVerification,
URL = $"https://dummy.yoma.world/verify/item/{myOpportunity.OpportunityId}/{myOpportunity.OrganizationId}",
URL = _notificationURLFactory.OpportunityVerificationItemURL(type, myOpportunity.OpportunityId, myOpportunity.OrganizationId),
ZltoReward = myOpportunity.ZltoReward,
YomaReward = myOpportunity.YomaReward
}
]
};

await _service.Send(type, recipients, data);
await _notificationDeliveryService.Send(type, CreateTestRecipients(), data);
}

private async Task SendOpportunityPublishedNotification_WhatsAppOnly(Opportunity opportunity)
{
var countryWorldwideId = _countryService.GetByCodeAplha2(Country.Worldwide.ToDescription()).Id;

var data = new NotificationOpportunityPublished
{
URLOpportunitiesPublic = _notificationURLFactory.OpportunitiesPublicURL(NotificationType.Opportunity_Published, [countryWorldwideId]),
Opportunities = [
new NotificationOpportunityPublishedItem
{
Id = opportunity.Id,
Title = opportunity.Title,
DateStart = opportunity.DateStart,
DateEnd = opportunity.DateEnd,
URL = _notificationURLFactory.OpportunityPublishedItemURL(NotificationType.Opportunity_Published, opportunity.Id, opportunity.OrganizationId),
ZltoReward = opportunity.ZltoReward,
YomaReward = opportunity.YomaReward
}
]
};

await _notificationDeliveryService.Send(NotificationType.Opportunity_Published, CreateTestRecipients(), data);
}
#endregion
}
}
0