Skip to content

Commit 211cc14

Browse files
author
Cédric Belin
committed
Add the Client class and its tests
1 parent a738b4c commit 211cc14

File tree

3 files changed

+163
-2
lines changed

3 files changed

+163
-2
lines changed

src/client.cs

Lines changed: 83 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
namespace Belin.Akismet;
22

3+
using System.Net.Http.Headers;
4+
35
/// <summary>
46
/// Submits comments to the Akismet service.
57
/// </summary>
@@ -41,5 +43,85 @@ public class Client(string apiKey, Blog blog, string baseUrl = "https://rest.aki
4143
/// <summary>
4244
/// The user agent string to use when making requests.
4345
/// </summary>
44-
public string UserAgent { get; init; } = $".NET/{Environment.Version} | Akismet/{Version}";
46+
public string UserAgent { get; init; } = $".NET/{Environment.Version.ToString(3)} | Akismet/{Version}";
47+
48+
/// <summary>
49+
/// Checks the specified comment against the service database, and returns a value indicating whether it is spam.
50+
/// </summary>
51+
/// <param name="comment">The comment to be submitted.</param>
52+
/// <param name="cancellationToken">The token to cancel the operation.</param>
53+
/// <returns>A value indicating whether the specified comment is spam.</returns>
54+
/// <exception cref="HttpRequestException">The remote server returned an invalid response.</exception>
55+
public async Task<CheckResult> CheckComment(Comment comment, CancellationToken cancellationToken = default) {
56+
using var response = await Fetch("comment-check", comment.ToJson(), cancellationToken);
57+
if (await response.Content.ReadAsStringAsync(cancellationToken) == "false") return CheckResult.Ham;
58+
if (!response.Headers.TryGetValues("X-akismet-pro-tip", out var proTips)) return CheckResult.Spam;
59+
return proTips.First() == "discard" ? CheckResult.PervasiveSpam : CheckResult.Spam;
60+
}
61+
62+
/// <summary>
63+
/// Submits the specified comment that was incorrectly marked as spam but should not have been.
64+
/// </summary>
65+
/// <param name="comment">The comment to be submitted.</param>
66+
/// <param name="cancellationToken">The token to cancel the operation.</param>
67+
/// <returns>Completes once the comment has been submitted.</returns>
68+
/// <exception cref="HttpRequestException">The remote server returned an invalid response.</exception>
69+
public async Task SubmitHam(Comment comment, CancellationToken cancellationToken = default) {
70+
using var response = await Fetch("1.1/submit-ham", comment.ToJson(), cancellationToken);
71+
var body = await response.Content.ReadAsStringAsync(cancellationToken);
72+
if (body != Success) throw new HttpRequestException("Invalid server response.");
73+
}
74+
75+
/// <summary>
76+
/// Submits the specified comment that was not marked as spam but should have been.
77+
/// </summary>
78+
/// <param name="comment">The comment to be submitted.</param>
79+
/// <param name="cancellationToken">The token to cancel the operation.</param>
80+
/// <returns>Completes once the comment has been submitted.</returns>
81+
/// <exception cref="HttpRequestException">The remote server returned an invalid response.</exception>
82+
public async Task SubmitSpam(Comment comment, CancellationToken cancellationToken = default) {
83+
using var response = await Fetch("1.1/submit-spam", comment.ToJson(), cancellationToken);
84+
var body = await response.Content.ReadAsStringAsync(cancellationToken);
85+
if (body != Success) throw new HttpRequestException("Invalid server response.");
86+
}
87+
88+
/// <summary>
89+
/// Checks the API key against the service database, and returns a value indicating whether it is valid.
90+
/// </summary>
91+
/// <param name="cancellationToken">The token to cancel the operation.</param>
92+
/// <returns><see langword="true"/> if the specified API key is valid, otherwise <see langword="false"/>.</returns>
93+
public async Task<bool> VerifyKey(CancellationToken cancellationToken = default) {
94+
try {
95+
using var response = await Fetch("1.1/verify-key", cancellationToken: cancellationToken);
96+
return await response.Content.ReadAsStringAsync(cancellationToken) == "valid";
97+
}
98+
catch {
99+
return false;
100+
}
101+
}
102+
103+
/// <summary>
104+
/// Queries the service by posting the specified fields to a given end point, and returns the response.
105+
/// </summary>
106+
/// <param name="endpoint">The relative URL of the end point to query.</param>
107+
/// <param name="fields">The fields describing the query body.</param>
108+
/// <param name="cancellationToken">The token to cancel the operation.</param>
109+
/// <returns>The server response.</returns>
110+
/// <exception cref="HttpRequestException">An error occurred while querying the end point.</exception>
111+
private async Task<HttpResponseMessage> Fetch(string endpoint, IDictionary<string, string>? fields = null, CancellationToken cancellationToken = default) {
112+
var postFields = Blog.ToJson();
113+
postFields.Add("api_key", ApiKey);
114+
if (IsTest) postFields.Add("is_test", "1");
115+
if (fields is not null) foreach (var item in fields) postFields.Add(item.Key, item.Value);
116+
117+
using var request = new HttpRequestMessage(HttpMethod.Post, new Uri(BaseUrl, endpoint)) { Content = new FormUrlEncodedContent(postFields) };
118+
request.Headers.Add("User-Agent", UserAgent);
119+
120+
using var httpClient = new HttpClient();
121+
var response = await httpClient.SendAsync(request, cancellationToken);
122+
response.EnsureSuccessStatusCode();
123+
if (response.Headers.TryGetValues("X-akismet-alert-msg", out var alertMessages)) throw new HttpRequestException(alertMessages.First());
124+
if (response.Headers.TryGetValues("X-akismet-debug-help", out var debugHelps)) throw new HttpRequestException(debugHelps.First());
125+
return response;
126+
}
45127
}

src/comment.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@ public class Comment(Author author) {
5858
internal IDictionary<string, string> ToJson() {
5959
var map = Author.ToJson();
6060
if (!string.IsNullOrWhiteSpace(Content)) map["comment_content"] = Content;
61-
// TODO if (Context.Count > 0) map["comment_context"] = Context.ToString();
61+
// TODO if (Context.Count > 0) map["comment_context"] = string.Join(',', Context);
6262
if (Date is not null) map["comment_date_gmt"] = Date?.ToUniversalTime().ToString("yyyy-MM-ddTHH:mm:ssZ")!;
6363
if (Permalink is not null) map["permalink"] = Permalink.ToString();
6464
if (PostModified is not null) map["comment_post_modified_gmt"] = PostModified?.ToUniversalTime().ToString("yyyy-MM-ddTHH:mm:ssZ")!;

test/client_test.cs

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,80 @@
11
namespace Belin.Akismet;
2+
3+
using System;
4+
using System.Threading.Tasks;
5+
6+
/// <summary>
7+
/// Tests the features of the <see cref="Client"/> class.
8+
/// </summary>
9+
[TestClass]
10+
public sealed class ClientTest {
11+
12+
/// <summary>
13+
/// The client used to query the remote API.
14+
/// </summary>
15+
private readonly Client client;
16+
17+
/// <summary>
18+
/// A comment with content marked as ham.
19+
/// </summary>
20+
private readonly Comment ham;
21+
22+
/// <summary>
23+
/// A comment with content marked as spam.
24+
/// </summary>
25+
private readonly Comment spam;
26+
27+
/// <summary>
28+
/// Creates a new test.
29+
/// </summary>
30+
public ClientTest() {
31+
client = new Client(Environment.GetEnvironmentVariable("AKISMET_API_KEY")!, new Blog("https://github.com/cedx/akismet.cs")) { IsTest = true };
32+
33+
var hamAuthor = new Author(ipAddress: "192.168.0.1") {
34+
Name = "Akismet",
35+
Role = AuthorRole.Administrator,
36+
Url = new Uri("https://belin.io"),
37+
UserAgent = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/133.0.0.0 Safari/537.36 Edg/133.0.0.0"
38+
};
39+
40+
ham = new Comment(hamAuthor) {
41+
Content = "I'm testing out the Service API.",
42+
Referrer = new Uri("https://www.nuget.org/packages/Belin.Akismet"),
43+
Type = CommentType.Comment
44+
};
45+
46+
var spamAuthor = new Author(ipAddress: "127.0.0.1") {
47+
Email = "akismet-guaranteed-spam@example.com",
48+
Name = "viagra-test-123",
49+
UserAgent = "Spam Bot/6.6.6"
50+
};
51+
52+
spam = new Comment(spamAuthor) { Content = "Spam!", Type = CommentType.BlogPost };
53+
}
54+
55+
[TestMethod]
56+
public async Task CheckComment() {
57+
AreEqual(CheckResult.Ham, await client.CheckComment(ham));
58+
59+
var result = await client.CheckComment(spam);
60+
IsTrue(result == CheckResult.Spam || result == CheckResult.PervasiveSpam);
61+
}
62+
63+
[TestMethod]
64+
public async Task SubmitHam() {
65+
await client.SubmitHam(ham);
66+
}
67+
68+
[TestMethod]
69+
public async Task SubmitSpam() {
70+
await client.SubmitSpam(spam);
71+
}
72+
73+
[TestMethod]
74+
public async Task VerifyKey() {
75+
IsTrue(await client.VerifyKey());
76+
77+
var newClient = new Client("0123456789-ABCDEF", client.Blog) { IsTest = true };
78+
IsFalse(await newClient.VerifyKey());
79+
}
80+
}

0 commit comments

Comments
 (0)