diff --git a/.config/dotnet-tools.json b/.config/dotnet-tools.json
new file mode 100644
index 0000000..307b712
--- /dev/null
+++ b/.config/dotnet-tools.json
@@ -0,0 +1,13 @@
+{
+ "version": 1,
+ "isRoot": true,
+ "tools": {
+ "dotnet-reportgenerator-globaltool": {
+ "version": "5.5.10",
+ "commands": [
+ "reportgenerator"
+ ],
+ "rollForward": false
+ }
+ }
+}
\ No newline at end of file
diff --git a/.github/workflows/dotnet.yaml b/.github/workflows/dotnet.yaml
index c332d31..eb31b4c 100644
--- a/.github/workflows/dotnet.yaml
+++ b/.github/workflows/dotnet.yaml
@@ -22,4 +22,26 @@ jobs:
- name: Lint
run: ./scripts/lint.sh
- name: Test
- run: dotnet test --no-build --verbosity normal
+ run: dotnet test --no-build --coverage --coverage-output-format cobertura --coverage-output coverage.cobertura.xml --verbosity normal
+ - name: Generate coverage report
+ run: dotnet reportgenerator "-reports:**/TestResults/coverage.cobertura.xml" "-targetdir:coverage-report" "-reporttypes:Html;Badges;TextSummary"
+ - name: Upload Pages artifact
+ if: github.ref == 'refs/heads/develop' || github.ref == 'refs/heads/master'
+ uses: actions/upload-pages-artifact@v3
+ with:
+ path: ./coverage-report
+
+ deploy:
+ needs: build
+ if: github.ref == 'refs/heads/develop' || github.ref == 'refs/heads/master'
+ runs-on: ubuntu-latest
+ permissions:
+ pages: write
+ id-token: write
+ environment:
+ name: github-pages
+ url: ${{ steps.deployment.outputs.page_url }}
+ steps:
+ - name: Deploy to GitHub Pages
+ id: deployment
+ uses: actions/deploy-pages@v4
diff --git a/.gitignore b/.gitignore
index 3e759b7..25f5224 100644
--- a/.gitignore
+++ b/.gitignore
@@ -24,6 +24,12 @@ bld/
[Oo]bj/
[Ll]og/
+# Test / coverage outputs
+[Tt]est[Rr]esults/
+coverage-report/
+*.cobertura.xml
+*.coverage
+
# Visual Studio 2015/2017 cache/options directory
.vs/
# Uncomment if you have tasks that create the project's static files in wwwroot
diff --git a/Directory.Packages.props b/Directory.Packages.props
index cdaa82f..3d74bec 100644
--- a/Directory.Packages.props
+++ b/Directory.Packages.props
@@ -10,10 +10,10 @@
-
-
-
-
+
+
+
+
diff --git a/README.md b/README.md
index 747d91f..8b1a96c 100644
--- a/README.md
+++ b/README.md
@@ -2,10 +2,10 @@
[](https://www.nuget.org/packages/SharpChatwork/)
-|Branch|Status|
-|------|------|
-|master|[](https://github.com/Egliss/SharpChatwork/actions/workflows/dotnet.yaml)|
-|develop|[](https://github.com/Egliss/SharpChatwork/actions/workflows/dotnet.yaml)|
+|Branch|Status|Coverage|
+|------|------|--------|
+|master|[](https://github.com/Egliss/SharpChatwork/actions/workflows/dotnet.yaml)|[](https://egliss.github.io/SharpChatwork/)|
+|develop|[](https://github.com/Egliss/SharpChatwork/actions/workflows/dotnet.yaml)|[](https://egliss.github.io/SharpChatwork/)|
## API Support Status
diff --git a/SharpChatwork.Test/SharpChatwork.Test.csproj b/SharpChatwork.Test/SharpChatwork.Test.csproj
index b6f859a..29b1a31 100644
--- a/SharpChatwork.Test/SharpChatwork.Test.csproj
+++ b/SharpChatwork.Test/SharpChatwork.Test.csproj
@@ -5,21 +5,17 @@
enable
enable
+ Exe
false
true
+ $(NoWarn);CA1707;CA2012
-
-
-
- runtime; build; native; contentfiles; analyzers; buildtransitive
- all
-
-
- runtime; build; native; contentfiles; analyzers; buildtransitive
- all
-
+
+
+
+
diff --git a/SharpChatwork.Test/src/Client/AccessTokenClientTests.cs b/SharpChatwork.Test/src/Client/AccessTokenClientTests.cs
new file mode 100644
index 0000000..9674bd5
--- /dev/null
+++ b/SharpChatwork.Test/src/Client/AccessTokenClientTests.cs
@@ -0,0 +1,65 @@
+using System.Text.Json;
+using SharpChatwork.Client.Exceptions;
+using SharpChatwork.Test.Helpers;
+
+namespace SharpChatwork.Test.Client;
+
+public class AccessTokenClientTests
+{
+ [Test]
+ public async Task QueryAsync_sends_x_chatworktoken_header_and_deserializes_response()
+ {
+ var (client, handler) = MockedAccessTokenClient.Create("my-token");
+ handler.Expect(HttpMethod.Get, EndPoints.Me.ToString())
+ .WithHeaders("X-ChatWorkToken", "my-token")
+ .Respond("application/json", ApiFixtures.MeGet);
+
+ var user = await client.me.GetUserAsync();
+
+ await Assert.That(user.account_id).IsEqualTo(123);
+ await Assert.That(user.name).IsEqualTo("John Smith");
+ handler.VerifyNoOutstandingExpectation();
+ }
+
+ [Test]
+ public async Task QueryAsync_throws_ChatworkClientException_on_non_success_status()
+ {
+ var (client, handler) = MockedAccessTokenClient.Create();
+ handler.When(EndPoints.Me.ToString())
+ .Respond(HttpStatusCode.InternalServerError, "application/json", ApiFixtures.ErrorResponse);
+
+ await Assert.That(async () => await client.me.GetUserAsync())
+ .ThrowsExactly();
+ }
+
+ [Test]
+ public async Task QueryAsync_throws_JsonException_on_malformed_response()
+ {
+ var (client, handler) = MockedAccessTokenClient.Create();
+ handler.When(EndPoints.Me.ToString())
+ .Respond("application/json", "{not-json");
+
+ await Assert.That(async () => await client.me.GetUserAsync())
+ .Throws();
+ }
+
+ [Test]
+ public async Task QueryAsync_uses_specified_http_method_for_post_request()
+ {
+ var (client, handler) = MockedAccessTokenClient.Create();
+ handler.Expect(HttpMethod.Post, EndPoints.RoomMessages(42).ToString())
+ .Respond("application/json", ApiFixtures.MessageIdResult);
+
+ var result = await client.room.message.SendAsync(42, "hi", isSelfUnread: false);
+
+ await Assert.That(result.message_id).IsEqualTo("9876");
+ handler.VerifyNoOutstandingExpectation();
+ }
+
+ [Test]
+ public async Task ClientName_returns_class_name()
+ {
+ var (client, _) = MockedAccessTokenClient.Create();
+ await Assert.That(client.clientName).IsEqualTo(nameof(AccessToken.AccessTokenClient));
+ }
+}
diff --git a/SharpChatwork.Test/src/Client/OAuth2ClientTests.cs b/SharpChatwork.Test/src/Client/OAuth2ClientTests.cs
new file mode 100644
index 0000000..f5bd841
--- /dev/null
+++ b/SharpChatwork.Test/src/Client/OAuth2ClientTests.cs
@@ -0,0 +1,51 @@
+using System.Text;
+using SharpChatwork.OAuth2;
+using SharpChatwork.Test.Helpers;
+
+namespace SharpChatwork.Test.Client;
+
+public class OAuth2ClientTests
+{
+ [Test]
+ public async Task UpdateTokenAsync_uses_basic_auth_with_client_and_secret()
+ {
+ var (client, handler) = MockedOAuth2Client.Create("ck", "sk");
+ var expected = "Basic " + Convert.ToBase64String(Encoding.ASCII.GetBytes("ck:sk"));
+ handler.Expect(HttpMethod.Post, EndPoints.Token.ToString())
+ .WithHeaders("Authorization", expected)
+ .Respond("application/json", ApiFixtures.OAuth2TokenResult);
+
+ var result = await client.UpdateTokenAsync(OAuth2TokenQuery.GrantType.RefreshToken);
+
+ await Assert.That(result.access_token).IsEqualTo("abc-access");
+ await Assert.That(result.refresh_token).IsEqualTo("abc-refresh");
+ await Assert.That(result.expires_in).IsEqualTo(3600L);
+ await Assert.That(result.isError).IsFalse();
+ handler.VerifyNoOutstandingExpectation();
+ }
+
+ [Test]
+ public async Task QueryAsync_sends_Bearer_authorization_with_access_token()
+ {
+ var (client, handler) = MockedOAuth2Client.Create();
+ handler.When(HttpMethod.Post, EndPoints.Token.ToString())
+ .Respond("application/json", ApiFixtures.OAuth2TokenResult);
+ await client.UpdateTokenAsync(OAuth2TokenQuery.GrantType.RefreshToken);
+
+ handler.Expect(HttpMethod.Get, EndPoints.Me.ToString())
+ .WithHeaders("Authorization", "Bearer abc-access")
+ .Respond("application/json", ApiFixtures.MeGet);
+
+ var user = await client.me.GetUserAsync();
+
+ await Assert.That(user.account_id).IsEqualTo(123);
+ handler.VerifyNoOutstandingExpectation();
+ }
+
+ [Test]
+ public async Task ClientName_returns_class_name()
+ {
+ var (client, _) = MockedOAuth2Client.Create();
+ await Assert.That(client.clientName).IsEqualTo(nameof(OAuth2Client));
+ }
+}
diff --git a/SharpChatwork.Test/src/Helpers/ApiFixtures.cs b/SharpChatwork.Test/src/Helpers/ApiFixtures.cs
new file mode 100644
index 0000000..4f83518
--- /dev/null
+++ b/SharpChatwork.Test/src/Helpers/ApiFixtures.cs
@@ -0,0 +1,296 @@
+namespace SharpChatwork.Test.Helpers;
+
+internal static class ApiFixtures
+{
+ public const string MeGet = """
+ {
+ "account_id": 123,
+ "room_id": 322,
+ "name": "John Smith",
+ "chatwork_id": "tarochatworkid",
+ "organization_id": 101,
+ "organization_name": "Hello Company",
+ "department": "Marketing",
+ "title": "CMO",
+ "url": "http://mycompany.example.com",
+ "introduction": "Self Introduction",
+ "mail": "taro@example.com",
+ "tel_organization": "XXX-XXXX-XXXX",
+ "tel_extension": "YYY-YYYY-YYYY",
+ "tel_mobile": "ZZZ-ZZZZ-ZZZZ",
+ "skype": "myskype_id",
+ "facebook": "myfacebook_id",
+ "twitter": "mytwitter_id",
+ "avatar_image_url": "https://example.com/abc.png",
+ "login_mail": "account@example.com"
+ }
+ """;
+
+ public const string MyStatusGet = """
+ {
+ "unread_room_num": 2,
+ "mention_room_num": 1,
+ "mytask_room_num": 3,
+ "unread_num": 12,
+ "mention_num": 1,
+ "mytask_num": 8
+ }
+ """;
+
+ public const string MyTasksGet = """
+ [
+ {
+ "task_id": 3,
+ "room": { "room_id": 5, "name": "Group Chat Name", "icon_path": "https://example.com/ico.png" },
+ "assigned_by_account": { "account_id": 78, "name": "Anna", "avatar_image_url": "https://example.com/anna.png" },
+ "message_id": "13",
+ "body": "buy milk",
+ "limit_time": 1384354799,
+ "status": "open",
+ "limit_type": "date"
+ }
+ ]
+ """;
+
+ public const string ContactsGet = """
+ [
+ {
+ "account_id": 123,
+ "room_id": 322,
+ "name": "John Smith",
+ "chatwork_id": "tarochatworkid",
+ "organization_id": 101,
+ "organization_name": "Hello Company",
+ "department": "Marketing",
+ "avatar_image_url": "https://example.com/abc.png"
+ }
+ ]
+ """;
+
+ public const string RoomsGet = """
+ [
+ {
+ "room_id": 123,
+ "name": "Group Chat Name",
+ "type": "group",
+ "role": "admin",
+ "sticky": false,
+ "unread_num": 10,
+ "mention_num": 1,
+ "mytask_num": 0,
+ "message_num": 122,
+ "file_num": 10,
+ "task_num": 17,
+ "icon_path": "https://example.com/ico.png",
+ "last_update_time": 1298905200
+ }
+ ]
+ """;
+
+ public const string RoomGet = """
+ {
+ "room_id": 123,
+ "name": "Group Chat Name",
+ "type": "group",
+ "role": "admin",
+ "sticky": false,
+ "unread_num": 10,
+ "mention_num": 1,
+ "mytask_num": 0,
+ "message_num": 122,
+ "file_num": 10,
+ "task_num": 17,
+ "icon_path": "https://example.com/ico.png",
+ "last_update_time": 1298905200
+ }
+ """;
+
+ public const string RoomIdResult = """
+ {
+ "room_id": "1234"
+ }
+ """;
+
+ public const string RoomMembersGet = """
+ [
+ {
+ "account_id": 123,
+ "role": "admin",
+ "name": "John Smith",
+ "chatwork_id": "tarochatworkid",
+ "organization_id": 101,
+ "organization_name": "Hello Company",
+ "department": "Marketing",
+ "avatar_image_url": "https://example.com/abc.png"
+ }
+ ]
+ """;
+
+ public const string RoomMessagesGet = """
+ [
+ {
+ "message_id": "5",
+ "account": {
+ "account_id": 123,
+ "name": "Bob",
+ "avatar_image_url": "https://example.com/bob.png"
+ },
+ "body": "Hello Chatwork!",
+ "send_time": 1384242850,
+ "update_time": 0
+ }
+ ]
+ """;
+
+ public const string RoomMessageGet = """
+ {
+ "message_id": "5",
+ "account": {
+ "account_id": 123,
+ "name": "Bob",
+ "avatar_image_url": "https://example.com/bob.png"
+ },
+ "body": "Hello Chatwork!",
+ "send_time": 1384242850,
+ "update_time": 0
+ }
+ """;
+
+ public const string MessageIdResult = """
+ {
+ "message_id": "9876"
+ }
+ """;
+
+ public const string MessageReadUnreadResult = """
+ {
+ "unread_num": 3,
+ "mention_num": 1
+ }
+ """;
+
+ public const string RoomTasksGet = """
+ [
+ {
+ "task_id": 3,
+ "room": { "room_id": 5, "name": "Group Chat", "icon_path": "https://example.com/ico.png" },
+ "assigned_by_account": { "account_id": 78, "name": "Anna", "avatar_image_url": "https://example.com/anna.png" },
+ "message_id": "13",
+ "body": "buy milk",
+ "limit_time": 1384354799,
+ "status": "open",
+ "limit_type": "date"
+ }
+ ]
+ """;
+
+ public const string RoomTaskGet = """
+ {
+ "task_id": 3,
+ "room": { "room_id": 5, "name": "Group Chat", "icon_path": "https://example.com/ico.png" },
+ "assigned_by_account": { "account_id": 78, "name": "Anna", "avatar_image_url": "https://example.com/anna.png" },
+ "message_id": "13",
+ "body": "buy milk",
+ "limit_time": 1384354799,
+ "status": "open",
+ "limit_type": "date"
+ }
+ """;
+
+ public const string TaskIdResult = """
+ {
+ "task_id": "1234"
+ }
+ """;
+
+ public const string RoomFilesGet = """
+ [
+ {
+ "file_id": 3,
+ "account": {
+ "account_id": 123,
+ "name": "Bob",
+ "avatar_image_url": "https://example.com/bob.png"
+ },
+ "message_id": "22",
+ "filename": "README.md",
+ "filesize": 21,
+ "upload_time": 1384164237
+ }
+ ]
+ """;
+
+ public const string RoomFileGet = """
+ {
+ "file_id": 3,
+ "account": {
+ "account_id": 123,
+ "name": "Bob",
+ "avatar_image_url": "https://example.com/bob.png"
+ },
+ "message_id": "22",
+ "filename": "README.md",
+ "filesize": 21,
+ "upload_time": 1384164237
+ }
+ """;
+
+ public const string FileIdResult = """
+ {
+ "file_id": "1234"
+ }
+ """;
+
+ public const string InviteLinkGet = """
+ {
+ "public": true,
+ "url": "https://example.com/abc123",
+ "need_acceptance": true,
+ "description": "Welcome"
+ }
+ """;
+
+ public const string IncomingRequestsGet = """
+ [
+ {
+ "request_id": 1,
+ "account_id": 2,
+ "message": "Please add me!",
+ "name": "Mike",
+ "chatwork_id": "mike-id",
+ "organization_id": 101,
+ "organization_name": "Hello Company",
+ "department": "Engineering",
+ "avatar_image_url": "https://example.com/mike.png"
+ }
+ ]
+ """;
+
+ public const string IncomingRequestPut = """
+ {
+ "request_id": 1,
+ "account_id": 2,
+ "message": "Please add me!",
+ "name": "Mike",
+ "chatwork_id": "mike-id",
+ "organization_id": 101,
+ "organization_name": "Hello Company",
+ "department": "Engineering",
+ "avatar_image_url": "https://example.com/mike.png"
+ }
+ """;
+
+ public const string OAuth2TokenResult = """
+ {
+ "access_token": "abc-access",
+ "refresh_token": "abc-refresh",
+ "token_type": "bearer",
+ "expires_in": 3600,
+ "scope": "rooms.all:read_write"
+ }
+ """;
+
+ public const string ErrorResponse = """
+ { "errors": ["Invalid API token"] }
+ """;
+}
diff --git a/SharpChatwork.Test/src/Helpers/MockedAccessTokenClient.cs b/SharpChatwork.Test/src/Helpers/MockedAccessTokenClient.cs
new file mode 100644
index 0000000..b9cd435
--- /dev/null
+++ b/SharpChatwork.Test/src/Helpers/MockedAccessTokenClient.cs
@@ -0,0 +1,15 @@
+using SharpChatwork.AccessToken;
+
+namespace SharpChatwork.Test.Helpers;
+
+internal static class MockedAccessTokenClient
+{
+ public const string DefaultToken = "test-token";
+
+ public static (AccessTokenClient Client, MockHttpMessageHandler Handler) Create(string token = DefaultToken)
+ {
+ var handler = new MockHttpMessageHandler();
+ var http = new HttpClient(handler);
+ return (new AccessTokenClient(token, http), handler);
+ }
+}
diff --git a/SharpChatwork.Test/src/Helpers/MockedOAuth2Client.cs b/SharpChatwork.Test/src/Helpers/MockedOAuth2Client.cs
new file mode 100644
index 0000000..f9ff3ee
--- /dev/null
+++ b/SharpChatwork.Test/src/Helpers/MockedOAuth2Client.cs
@@ -0,0 +1,18 @@
+using SharpChatwork.OAuth2;
+
+namespace SharpChatwork.Test.Helpers;
+
+internal static class MockedOAuth2Client
+{
+ public const string DefaultClientKey = "client-key";
+ public const string DefaultSecretKey = "secret-key";
+
+ public static (OAuth2Client Client, MockHttpMessageHandler Handler) Create(
+ string clientKey = DefaultClientKey,
+ string secretKey = DefaultSecretKey)
+ {
+ var handler = new MockHttpMessageHandler();
+ var http = new HttpClient(handler);
+ return (new OAuth2Client(clientKey, secretKey, http), handler);
+ }
+}
diff --git a/SharpChatwork.Test/src/Helpers/StubChatworkClient.cs b/SharpChatwork.Test/src/Helpers/StubChatworkClient.cs
new file mode 100644
index 0000000..c2a9132
--- /dev/null
+++ b/SharpChatwork.Test/src/Helpers/StubChatworkClient.cs
@@ -0,0 +1,64 @@
+namespace SharpChatwork.Test.Helpers;
+
+internal static class StubChatworkClient
+{
+ public static ChatworkClient WithJsonResponse(string json, int statusCode = 200)
+ {
+ var stub = Substitute.For();
+ stub.QueryAsync(
+ Arg.Any(),
+ Arg.Any(),
+ Arg.Any(),
+ Arg.Any())
+ .Returns(_ => new ValueTask(new ResponseWrapper
+ {
+ statusCode = statusCode,
+ content = json,
+ headers = new Dictionary>(),
+ }));
+ return stub;
+ }
+
+ public static async Task> ReadFormValuesAsync(HttpContent? content)
+ {
+ if(content is null)
+ {
+ return new Dictionary();
+ }
+ var raw = await content.ReadAsStringAsync();
+ var dict = new Dictionary();
+ foreach(var pair in raw.Split('&', StringSplitOptions.RemoveEmptyEntries))
+ {
+ var eq = pair.IndexOf('=');
+ if(eq < 0)
+ {
+ dict[Uri.UnescapeDataString(pair)] = string.Empty;
+ continue;
+ }
+ var key = Uri.UnescapeDataString(pair[..eq].Replace('+', ' '));
+ var value = Uri.UnescapeDataString(pair[(eq + 1)..].Replace('+', ' '));
+ dict[key] = value;
+ }
+ return dict;
+ }
+
+ public static HttpContent? LastQueryAsyncContent(ChatworkClient stub)
+ {
+ var call = stub.ReceivedCalls()
+ .Last(c => string.Equals(c.GetMethodInfo().Name, nameof(ChatworkClient.QueryAsync), StringComparison.Ordinal)
+ && c.GetArguments().Length == 4
+ && c.GetArguments()[2] is null or HttpContent);
+ return (HttpContent?)call.GetArguments()[2];
+ }
+
+ public static async Task AssertFormDataAsync(ChatworkClient stub, params (string Key, string Value)[] expected)
+ {
+ var content = LastQueryAsyncContent(stub);
+ var form = await ReadFormValuesAsync(content);
+ foreach(var (key, value) in expected)
+ {
+ var actual = form.TryGetValue(key, out var found) ? found : null;
+ await Assert.That(actual).IsEqualTo(value).Because($"form key '{key}' should equal '{value}'");
+ }
+ }
+}
diff --git a/SharpChatwork.Test/src/Query/ContactQueryTests.cs b/SharpChatwork.Test/src/Query/ContactQueryTests.cs
new file mode 100644
index 0000000..47f9c93
--- /dev/null
+++ b/SharpChatwork.Test/src/Query/ContactQueryTests.cs
@@ -0,0 +1,25 @@
+using SharpChatwork.Test.Helpers;
+
+namespace SharpChatwork.Test.Query;
+
+public class ContactQueryTests
+{
+ [Test]
+ public async Task GetAllAsync_calls_contacts_endpoint_with_GET()
+ {
+ var stub = StubChatworkClient.WithJsonResponse(ApiFixtures.ContactsGet);
+ var query = new ContactQuery(stub);
+
+ var contacts = (await query.GetAllAsync()).ToList();
+
+ await Assert.That(contacts.Count).IsEqualTo(1);
+ await Assert.That(contacts[0].account_id).IsEqualTo(123);
+ await Assert.That(contacts[0].chatwork_id).IsEqualTo("tarochatworkid");
+ await Assert.That(contacts[0].organization_name).IsEqualTo("Hello Company");
+ await stub.Received(1).QueryAsync(
+ EndPoints.Contacts,
+ HttpMethod.Get,
+ Arg.Any(),
+ Arg.Any());
+ }
+}
diff --git a/SharpChatwork.Test/src/Query/IncomingRequestQueryTests.cs b/SharpChatwork.Test/src/Query/IncomingRequestQueryTests.cs
new file mode 100644
index 0000000..80e85e3
--- /dev/null
+++ b/SharpChatwork.Test/src/Query/IncomingRequestQueryTests.cs
@@ -0,0 +1,58 @@
+using SharpChatwork.Test.Helpers;
+
+namespace SharpChatwork.Test.Query;
+
+public class IncomingRequestQueryTests
+{
+ [Test]
+ public async Task GetAllAsync_calls_incoming_requests_endpoint_with_GET()
+ {
+ var stub = StubChatworkClient.WithJsonResponse(ApiFixtures.IncomingRequestsGet);
+ var query = new IncomingRequestQuery(stub);
+
+ var requests = (await query.GetAllAsync()).ToList();
+
+ await Assert.That(requests.Count).IsEqualTo(1);
+ await Assert.That(requests[0].request_id).IsEqualTo(1);
+ await Assert.That(requests[0].account_id).IsEqualTo(2);
+ await Assert.That(requests[0].message).IsEqualTo("Please add me!");
+ await Assert.That(requests[0].name).IsEqualTo("Mike");
+ await Assert.That(requests[0].chatwork_id).IsEqualTo("mike-id");
+ await stub.Received(1).QueryAsync(
+ EndPoints.IncomingRequests,
+ HttpMethod.Get,
+ Arg.Any(),
+ Arg.Any());
+ }
+
+ [Test]
+ public async Task AcceptAsync_puts_to_incoming_requests_of_id()
+ {
+ var stub = StubChatworkClient.WithJsonResponse(ApiFixtures.IncomingRequestPut);
+ var query = new IncomingRequestQuery(stub);
+
+ var result = await query.AcceptAsync(7);
+
+ await Assert.That(result.request_id).IsEqualTo(1);
+ await stub.Received(1).QueryAsync(
+ EndPoints.IncomingRequestsOf(7),
+ HttpMethod.Put,
+ Arg.Any(),
+ Arg.Any());
+ }
+
+ [Test]
+ public async Task CancelAsync_deletes_incoming_requests_of_id()
+ {
+ var stub = StubChatworkClient.WithJsonResponse("{}");
+ var query = new IncomingRequestQuery(stub);
+
+ await query.CancelAsync(9);
+
+ await stub.Received(1).QueryAsync(
+ EndPoints.IncomingRequestsOf(9),
+ HttpMethod.Delete,
+ Arg.Any(),
+ Arg.Any());
+ }
+}
diff --git a/SharpChatwork.Test/src/Query/MeQueryTests.cs b/SharpChatwork.Test/src/Query/MeQueryTests.cs
new file mode 100644
index 0000000..eed9b1b
--- /dev/null
+++ b/SharpChatwork.Test/src/Query/MeQueryTests.cs
@@ -0,0 +1,59 @@
+using SharpChatwork.Test.Helpers;
+
+namespace SharpChatwork.Test.Query;
+
+public class MeQueryTests
+{
+ [Test]
+ public async Task GetUserAsync_calls_me_endpoint_with_GET()
+ {
+ var stub = StubChatworkClient.WithJsonResponse(ApiFixtures.MeGet);
+ var query = new MeQuery(stub);
+
+ var user = await query.GetUserAsync();
+
+ await Assert.That(user.account_id).IsEqualTo(123);
+ await Assert.That(user.chatwork_id).IsEqualTo("tarochatworkid");
+ await Assert.That(user.organization_name).IsEqualTo("Hello Company");
+ await Assert.That(user.login_mail).IsEqualTo("account@example.com");
+ await stub.Received(1).QueryAsync(
+ EndPoints.Me,
+ HttpMethod.Get,
+ Arg.Any(),
+ Arg.Any());
+ }
+
+ [Test]
+ public async Task GetMyStatusAsync_calls_my_status_endpoint_with_GET()
+ {
+ var stub = StubChatworkClient.WithJsonResponse(ApiFixtures.MyStatusGet);
+ var query = new MeQuery(stub);
+
+ var status = await query.GetMyStatusAsync();
+
+ await Assert.That(status.unread_room_num).IsEqualTo(2);
+ await Assert.That(status.mytask_num).IsEqualTo(8);
+ await stub.Received(1).QueryAsync(
+ EndPoints.MyStatus,
+ HttpMethod.Get,
+ Arg.Any(),
+ Arg.Any());
+ }
+
+ [Test]
+ public async Task GetMyTasksAsync_calls_my_tasks_endpoint_with_GET()
+ {
+ var stub = StubChatworkClient.WithJsonResponse(ApiFixtures.MyTasksGet);
+ var query = new MeQuery(stub);
+
+ var tasks = (await query.GetMyTasksAsync()).ToList();
+
+ await Assert.That(tasks.Count).IsEqualTo(1);
+ await Assert.That(tasks[0].task_id).IsEqualTo(3);
+ await stub.Received(1).QueryAsync(
+ EndPoints.MyTasks,
+ HttpMethod.Get,
+ Arg.Any(),
+ Arg.Any());
+ }
+}
diff --git a/SharpChatwork.Test/src/Query/RoomFileQueryTests.cs b/SharpChatwork.Test/src/Query/RoomFileQueryTests.cs
new file mode 100644
index 0000000..59e976c
--- /dev/null
+++ b/SharpChatwork.Test/src/Query/RoomFileQueryTests.cs
@@ -0,0 +1,59 @@
+using SharpChatwork.Test.Helpers;
+
+namespace SharpChatwork.Test.Query;
+
+public class RoomFileQueryTests
+{
+ [Test]
+ public async Task GetAllAsync_calls_files_endpoint_with_account_id_query_param()
+ {
+ var stub = StubChatworkClient.WithJsonResponse(ApiFixtures.RoomFilesGet);
+ var query = new RoomFileQuery(stub);
+
+ var files = (await query.GetAllAsync(42, accountId: 9)).ToList();
+
+ await Assert.That(files.Count).IsEqualTo(1);
+ await Assert.That(files[0].file_id).IsEqualTo(3);
+ await Assert.That(files[0].filename).IsEqualTo("README.md");
+ await Assert.That(files[0].filesize).IsEqualTo(21);
+ await Assert.That(files[0].message_id).IsEqualTo("22");
+ await stub.Received(1).QueryAsync(
+ Arg.Is(u => u.OriginalString.EndsWith("/rooms/42/files?account_id=9", StringComparison.Ordinal)),
+ HttpMethod.Get,
+ Arg.Any(),
+ Arg.Any());
+ }
+
+ [Test]
+ public async Task GetAsync_calls_files_of_id_endpoint_with_create_download_url_query()
+ {
+ var stub = StubChatworkClient.WithJsonResponse(ApiFixtures.RoomFileGet);
+ var query = new RoomFileQuery(stub);
+
+ var file = await query.GetAsync(42, 3, createDownloadLink: true);
+
+ await Assert.That(file.file_id).IsEqualTo(3);
+ await stub.Received(1).QueryAsync(
+ Arg.Is(u => u.OriginalString.EndsWith("/rooms/42/files/3?create_download_url=1", StringComparison.Ordinal)),
+ HttpMethod.Get,
+ Arg.Any(),
+ Arg.Any());
+ }
+
+ [Test]
+ public async Task UploadAsync_with_stream_posts_multipart_to_files_endpoint()
+ {
+ var stub = StubChatworkClient.WithJsonResponse(ApiFixtures.FileIdResult);
+ var query = new RoomFileQuery(stub);
+ using var stream = new System.IO.MemoryStream(new byte[] { 1, 2, 3 });
+
+ var result = await query.UploadAsync(42, stream, "test.txt", "hello");
+
+ await Assert.That(result.id).IsEqualTo("1234");
+ await stub.Received(1).QueryAsync(
+ EndPoints.RoomFiles(42),
+ HttpMethod.Post,
+ Arg.Is(c => c is System.Net.Http.MultipartFormDataContent),
+ Arg.Any());
+ }
+}
diff --git a/SharpChatwork.Test/src/Query/RoomInviteQueryTests.cs b/SharpChatwork.Test/src/Query/RoomInviteQueryTests.cs
new file mode 100644
index 0000000..8f4c3a6
--- /dev/null
+++ b/SharpChatwork.Test/src/Query/RoomInviteQueryTests.cs
@@ -0,0 +1,76 @@
+using SharpChatwork.Test.Helpers;
+
+namespace SharpChatwork.Test.Query;
+
+public class RoomInviteQueryTests
+{
+ [Test]
+ public async Task GetAsync_gets_room_link()
+ {
+ var stub = StubChatworkClient.WithJsonResponse(ApiFixtures.InviteLinkGet);
+ var query = new RoomInviteQuery(stub);
+
+ var link = await query.GetAsync(42);
+
+ await Assert.That(link.url).IsEqualTo("https://example.com/abc123");
+ await stub.Received(1).QueryAsync(
+ EndPoints.RoomLink(42),
+ HttpMethod.Get,
+ Arg.Any(),
+ Arg.Any());
+ }
+
+ [Test]
+ public async Task CreateAsync_posts_form_data_to_room_link_endpoint()
+ {
+ var stub = StubChatworkClient.WithJsonResponse(ApiFixtures.InviteLinkGet);
+ var query = new RoomInviteQuery(stub);
+
+ var link = await query.CreateAsync(42, "code123", "Welcome", requireAcceptance: true);
+
+ await Assert.That(link.need_acceptance).IsTrue();
+ await stub.Received(1).QueryAsync(
+ EndPoints.RoomLink(42),
+ HttpMethod.Post,
+ Arg.Any(),
+ Arg.Any());
+ await StubChatworkClient.AssertFormDataAsync(stub,
+ ("code", "code123"),
+ ("description", "Welcome"),
+ ("need_acceptance", "1"));
+ }
+
+ [Test]
+ public async Task UpdateAsync_puts_form_data_to_room_link_endpoint()
+ {
+ var stub = StubChatworkClient.WithJsonResponse(ApiFixtures.InviteLinkGet);
+ var query = new RoomInviteQuery(stub);
+
+ await query.UpdateAsync(42, "code456", "Hi", requireAcceptance: false);
+
+ await stub.Received(1).QueryAsync(
+ EndPoints.RoomLink(42),
+ HttpMethod.Put,
+ Arg.Any(),
+ Arg.Any());
+ await StubChatworkClient.AssertFormDataAsync(stub,
+ ("code", "code456"),
+ ("description", "Hi"),
+ ("need_acceptance", "0"));
+ }
+
+ [Test]
+ public async Task DestroyAsync_calls_room_link_with_DELETE()
+ {
+ var stub = StubChatworkClient.WithJsonResponse("{}");
+ var query = new RoomInviteQuery(stub);
+
+ await query.DestroyAsync(42);
+
+ await stub.Received(1).QueryAsync(
+ EndPoints.RoomLink(42),
+ HttpMethod.Delete,
+ Arg.Any(),
+ Arg.Any());
+ }
+}
diff --git a/SharpChatwork.Test/src/Query/RoomMemberQueryTests.cs b/SharpChatwork.Test/src/Query/RoomMemberQueryTests.cs
new file mode 100644
index 0000000..d2bfca5
--- /dev/null
+++ b/SharpChatwork.Test/src/Query/RoomMemberQueryTests.cs
@@ -0,0 +1,46 @@
+using SharpChatwork.Test.Helpers;
+
+namespace SharpChatwork.Test.Query;
+
+public class RoomMemberQueryTests
+{
+ [Test]
+ public async Task GetAllAsync_calls_room_member_endpoint_with_GET()
+ {
+ var stub = StubChatworkClient.WithJsonResponse(ApiFixtures.RoomMembersGet);
+ var query = new RoomMemberQuery(stub);
+
+ var members = (await query.GetAllAsync(123)).ToList();
+
+ await Assert.That(members.Count).IsEqualTo(1);
+ await Assert.That(members[0].account_id).IsEqualTo(123);
+ await stub.Received(1).QueryAsync(
+ EndPoints.RoomMember(123),
+ HttpMethod.Get,
+ Arg.Any(),
+ Arg.Any());
+ }
+
+ [Test]
+ public async Task UpdateAsync_puts_form_data_to_room_member_endpoint()
+ {
+ var stub = StubChatworkClient.WithJsonResponse("""{"admin":[10],"member":[20,21],"readonly":[30]}""");
+ var query = new RoomMemberQuery(stub);
+
+ var result = await query.UpdateAsync(42, [10L], [20L, 21L], [30L]);
+
+ await Assert.That(result.admin).Contains(10L);
+ await Assert.That(result.member).Contains(20L);
+ await Assert.That(result.member).Contains(21L);
+ await Assert.That(result.@readonly).Contains(30L);
+ await stub.Received(1).QueryAsync(
+ EndPoints.RoomMember(42),
+ HttpMethod.Put,
+ Arg.Any(),
+ Arg.Any());
+ await StubChatworkClient.AssertFormDataAsync(stub,
+ ("members_admin_ids", "10"),
+ ("members_member_ids", "20,21"),
+ ("members_readonly_ids", "30"));
+ }
+}
diff --git a/SharpChatwork.Test/src/Query/RoomMessageQueryTests.cs b/SharpChatwork.Test/src/Query/RoomMessageQueryTests.cs
new file mode 100644
index 0000000..9bf7779
--- /dev/null
+++ b/SharpChatwork.Test/src/Query/RoomMessageQueryTests.cs
@@ -0,0 +1,126 @@
+using SharpChatwork.Test.Helpers;
+
+namespace SharpChatwork.Test.Query;
+
+public class RoomMessageQueryTests
+{
+ [Test]
+ public async Task GetAllAsync_calls_messages_endpoint_with_force_query_param()
+ {
+ var stub = StubChatworkClient.WithJsonResponse(ApiFixtures.RoomMessagesGet);
+ var query = new RoomMessageQuery(stub);
+
+ var messages = (await query.GetAllAsync(42, isForceMode: true)).ToList();
+
+ await Assert.That(messages.Count).IsEqualTo(1);
+ await Assert.That(messages[0].body).IsEqualTo("Hello Chatwork!");
+ await stub.Received(1).QueryAsync(
+ Arg.Is(u => u.OriginalString.EndsWith("/rooms/42/messages?force=1", StringComparison.Ordinal)),
+ HttpMethod.Get,
+ Arg.Any(),
+ Arg.Any());
+ }
+
+ [Test]
+ public async Task GetAsync_calls_messages_of_id_with_GET()
+ {
+ var stub = StubChatworkClient.WithJsonResponse(ApiFixtures.RoomMessageGet);
+ var query = new RoomMessageQuery(stub);
+
+ var message = await query.GetAsync(42, 5);
+
+ await Assert.That(message.message_id).IsEqualTo("5");
+ await Assert.That(message.body).IsEqualTo("Hello Chatwork!");
+ await Assert.That(message.send_time).IsEqualTo(1384242850);
+ await Assert.That(message.account.account_id).IsEqualTo(123);
+ await Assert.That(message.account.name).IsEqualTo("Bob");
+ await stub.Received(1).QueryAsync(
+ EndPoints.RoomMessagesOf(42, 5),
+ HttpMethod.Get,
+ Arg.Any(),
+ Arg.Any());
+ }
+
+ [Test]
+ public async Task SendAsync_posts_body_and_self_unread_form_data()
+ {
+ var stub = StubChatworkClient.WithJsonResponse(ApiFixtures.MessageIdResult);
+ var query = new RoomMessageQuery(stub);
+
+ var result = await query.SendAsync(42, "hello", isSelfUnread: true);
+
+ await Assert.That(result.message_id).IsEqualTo("9876");
+ await stub.Received(1).QueryAsync(
+ EndPoints.RoomMessages(42),
+ HttpMethod.Post,
+ Arg.Any(),
+ Arg.Any());
+ await StubChatworkClient.AssertFormDataAsync(stub, ("body", "hello"), ("self_unread", "1"));
+ }
+
+ [Test]
+ public async Task UpdateAsync_puts_to_messages_of_id_with_body_form()
+ {
+ var stub = StubChatworkClient.WithJsonResponse(ApiFixtures.MessageIdResult);
+ var query = new RoomMessageQuery(stub);
+
+ var result = await query.UpdateAsync(42, 5, "updated");
+
+ await Assert.That(result.message_id).IsEqualTo("9876");
+ await stub.Received(1).QueryAsync(
+ EndPoints.RoomMessagesOf(42, 5),
+ HttpMethod.Put,
+ Arg.Any(),
+ Arg.Any());
+ await StubChatworkClient.AssertFormDataAsync(stub, ("body", "updated"));
+ }
+
+ [Test]
+ public async Task RemoveAsync_calls_messages_of_id_with_DELETE()
+ {
+ var stub = StubChatworkClient.WithJsonResponse(ApiFixtures.MessageIdResult);
+ var query = new RoomMessageQuery(stub);
+
+ await query.RemoveAsync(42, 5);
+
+ await stub.Received(1).QueryAsync(
+ EndPoints.RoomMessagesOf(42, 5),
+ HttpMethod.Delete,
+ Arg.Any(),
+ Arg.Any());
+ }
+
+ [Test]
+ public async Task ReadAsync_puts_to_messages_read_with_message_id_form()
+ {
+ var stub = StubChatworkClient.WithJsonResponse(ApiFixtures.MessageReadUnreadResult);
+ var query = new RoomMessageQuery(stub);
+
+ var result = await query.ReadAsync(42, 5);
+
+ await Assert.That(result.unread_num).IsEqualTo(3);
+ await stub.Received(1).QueryAsync(
+ EndPoints.RoomMessagesRead(42),
+ HttpMethod.Put,
+ Arg.Any(),
+ Arg.Any());
+ await StubChatworkClient.AssertFormDataAsync(stub, ("message_id", "5"));
+ }
+
+ [Test]
+ public async Task UnReadAsync_puts_to_messages_unread_with_message_id_form()
+ {
+ var stub = StubChatworkClient.WithJsonResponse(ApiFixtures.MessageReadUnreadResult);
+ var query = new RoomMessageQuery(stub);
+
+ var result = await query.UnReadAsync(42, 5);
+
+ await Assert.That(result.mention_num).IsEqualTo(1);
+ await stub.Received(1).QueryAsync(
+ EndPoints.RoomMessagesUnread(42),
+ HttpMethod.Put,
+ Arg.Any(),
+ Arg.Any());
+ await StubChatworkClient.AssertFormDataAsync(stub, ("message_id", "5"));
+ }
+}
diff --git a/SharpChatwork.Test/src/Query/RoomQueryTests.cs b/SharpChatwork.Test/src/Query/RoomQueryTests.cs
new file mode 100644
index 0000000..f46b99d
--- /dev/null
+++ b/SharpChatwork.Test/src/Query/RoomQueryTests.cs
@@ -0,0 +1,126 @@
+using SharpChatwork.Test.Helpers;
+
+namespace SharpChatwork.Test.Query;
+
+public class RoomQueryTests
+{
+ [Test]
+ public async Task GetAllAsync_calls_rooms_endpoint_with_GET()
+ {
+ var stub = StubChatworkClient.WithJsonResponse(ApiFixtures.RoomsGet);
+ var query = new RoomQuery(stub);
+
+ var rooms = (await query.GetAllAsync()).ToList();
+
+ await Assert.That(rooms.Count).IsEqualTo(1);
+ await Assert.That(rooms[0].room_id).IsEqualTo(123);
+ await stub.Received(1).QueryAsync(
+ EndPoints.Rooms,
+ HttpMethod.Get,
+ Arg.Any(),
+ Arg.Any());
+ }
+
+ [Test]
+ public async Task GetAsync_calls_room_of_id_with_GET()
+ {
+ var stub = StubChatworkClient.WithJsonResponse(ApiFixtures.RoomGet);
+ var query = new RoomQuery(stub);
+
+ var room = await query.GetAsync(123);
+
+ await Assert.That(room.room_id).IsEqualTo(123);
+ await Assert.That(room.name).IsEqualTo("Group Chat Name");
+ await Assert.That(room.type).IsEqualTo("group");
+ await Assert.That(room.role).IsEqualTo("admin");
+ await Assert.That(room.sticky).IsFalse();
+ await Assert.That(room.message_num).IsEqualTo(122);
+ await Assert.That(room.last_update_time).IsEqualTo(1298905200);
+ await stub.Received(1).QueryAsync(
+ EndPoints.RoomOf(123),
+ HttpMethod.Get,
+ Arg.Any(),
+ Arg.Any());
+ }
+
+ [Test]
+ public async Task CreateAsync_posts_to_rooms_endpoint()
+ {
+ var stub = StubChatworkClient.WithJsonResponse(ApiFixtures.RoomIdResult);
+ var query = new RoomQuery(stub);
+
+ var result = await query.CreateAsync();
+
+ await Assert.That(result.room_id).IsEqualTo("1234");
+ await stub.Received(1).QueryAsync(
+ EndPoints.Rooms,
+ HttpMethod.Post,
+ Arg.Any(),
+ Arg.Any());
+ }
+
+ [Test]
+ public async Task UpdateAsync_puts_form_data_to_room_of_id()
+ {
+ var stub = StubChatworkClient.WithJsonResponse(ApiFixtures.RoomIdResult);
+ var query = new RoomQuery(stub);
+
+ var result = await query.UpdateAsync(123, "new room", "desc", RoomIconPreset.Project);
+
+ await Assert.That(result.room_id).IsEqualTo("1234");
+ await stub.Received(1).QueryAsync(
+ EndPoints.RoomOf(123),
+ HttpMethod.Put,
+ Arg.Any(),
+ Arg.Any());
+ await StubChatworkClient.AssertFormDataAsync(stub,
+ ("name", "new room"),
+ ("description", "desc"),
+ ("icon_preset", "project"));
+ }
+
+ [Test]
+ public async Task LeaveAsync_deletes_room_of_id_with_action_type_leave()
+ {
+ var stub = StubChatworkClient.WithJsonResponse(ApiFixtures.RoomIdResult);
+ var query = new RoomQuery(stub);
+
+ await query.LeaveAsync(123);
+
+ await stub.Received(1).QueryAsync(
+ EndPoints.RoomOf(123),
+ HttpMethod.Delete,
+ Arg.Any(),
+ Arg.Any());
+ await StubChatworkClient.AssertFormDataAsync(stub, ("action_type", "leave"));
+ }
+
+ [Test]
+ public async Task DeleteAsync_deletes_room_of_id_with_action_type_delete()
+ {
+ var stub = StubChatworkClient.WithJsonResponse(ApiFixtures.RoomIdResult);
+ var query = new RoomQuery(stub);
+
+ await query.DeleteAsync(123);
+
+ await stub.Received(1).QueryAsync(
+ EndPoints.RoomOf(123),
+ HttpMethod.Delete,
+ Arg.Any(),
+ Arg.Any());
+ await StubChatworkClient.AssertFormDataAsync(stub, ("action_type", "delete"));
+ }
+
+ [Test]
+ public async Task SubQueries_are_instantiated_with_correct_types()
+ {
+ var stub = StubChatworkClient.WithJsonResponse("{}");
+ var query = new RoomQuery(stub);
+
+ await Assert.That(query.message).IsTypeOf();
+ await Assert.That(query.member).IsTypeOf();
+ await Assert.That(query.invite).IsTypeOf();
+ await Assert.That(query.file).IsTypeOf();
+ await Assert.That(query.task).IsTypeOf();
+ }
+}
diff --git a/SharpChatwork.Test/src/Query/RoomTaskQueryTests.cs b/SharpChatwork.Test/src/Query/RoomTaskQueryTests.cs
new file mode 100644
index 0000000..420b12d
--- /dev/null
+++ b/SharpChatwork.Test/src/Query/RoomTaskQueryTests.cs
@@ -0,0 +1,84 @@
+using SharpChatwork.Test.Helpers;
+
+namespace SharpChatwork.Test.Query;
+
+public class RoomTaskQueryTests
+{
+ [Test]
+ public async Task GetAllAsync_sends_account_id_and_status_as_query_string()
+ {
+ var stub = StubChatworkClient.WithJsonResponse(ApiFixtures.RoomTasksGet);
+ var query = new RoomTaskQuery(stub);
+
+ var tasks = (await query.GetAllAsync(42, accountId: 1, autherId: 2, isDone: false)).ToList();
+
+ await Assert.That(tasks.Count).IsEqualTo(1);
+ await stub.Received(1).QueryAsync(
+ Arg.Is(u =>
+ u.OriginalString.Contains("/rooms/42/tasks?", StringComparison.Ordinal)
+ && u.OriginalString.Contains("account_id=1", StringComparison.Ordinal)
+ && u.OriginalString.Contains("assigned_by_account_id=2", StringComparison.Ordinal)
+ && u.OriginalString.Contains("status=open", StringComparison.Ordinal)),
+ HttpMethod.Get,
+ Arg.Any(),
+ Arg.Any());
+ }
+
+ [Test]
+ public async Task GetAllAsync_uses_status_done_when_isDone_true()
+ {
+ var stub = StubChatworkClient.WithJsonResponse(ApiFixtures.RoomTasksGet);
+ var query = new RoomTaskQuery(stub);
+
+ await query.GetAllAsync(42, 1, 2, isDone: true);
+
+ await stub.Received(1).QueryAsync(
+ Arg.Is(u => u.OriginalString.Contains("status=done", StringComparison.Ordinal)),
+ HttpMethod.Get,
+ Arg.Any(),
+ Arg.Any());
+ }
+
+ [Test]
+ public async Task GetAsync_calls_room_task_of_id_with_GET()
+ {
+ var stub = StubChatworkClient.WithJsonResponse(ApiFixtures.RoomTaskGet);
+ var query = new RoomTaskQuery(stub);
+
+ var task = await query.GetAsync(42, 3);
+
+ await Assert.That(task.task_id).IsEqualTo(3);
+ await stub.Received(1).QueryAsync(
+ EndPoints.RoomTasksOf(42, 3),
+ HttpMethod.Get,
+ Arg.Any(),
+ Arg.Any());
+ }
+
+ [Test]
+ public async Task UpdateAsync_puts_to_task_status_with_body_form()
+ {
+ var stub = StubChatworkClient.WithJsonResponse(ApiFixtures.TaskIdResult);
+ var query = new RoomTaskQuery(stub);
+
+ var result = await query.UpdateAsync(42, 3, TaskStateType.Done);
+
+ await Assert.That(result.task_id).IsEqualTo("1234");
+ await stub.Received(1).QueryAsync(
+ EndPoints.RoomTasksOfStatus(42, 3),
+ HttpMethod.Put,
+ Arg.Any(),
+ Arg.Any());
+ await StubChatworkClient.AssertFormDataAsync(stub, ("body", "done"));
+ }
+
+ [Test]
+ public async Task CreateAsync_throws_NotImplementedException()
+ {
+ var stub = StubChatworkClient.WithJsonResponse("{}");
+ var query = new RoomTaskQuery(stub);
+
+ await Assert.That(async () => await query.CreateAsync(1, "body", 0))
+ .Throws();
+ }
+}
diff --git a/SharpChatwork.Test/src/Query/Spec/DtoSpecTests.cs b/SharpChatwork.Test/src/Query/Spec/DtoSpecTests.cs
new file mode 100644
index 0000000..27baf0a
--- /dev/null
+++ b/SharpChatwork.Test/src/Query/Spec/DtoSpecTests.cs
@@ -0,0 +1,25 @@
+namespace SharpChatwork.Test.Query.Spec;
+
+public class DtoSpecTests
+{
+ [Test]
+ public async Task Room_should_expose_description_per_spec()
+ {
+ var prop = typeof(Room).GetProperty("description");
+ await Assert.That(prop).IsNotNull();
+ }
+
+ [Test]
+ public async Task UserTask_should_expose_account_per_spec()
+ {
+ var prop = typeof(UserTask).GetProperty("account");
+ await Assert.That(prop).IsNotNull();
+ }
+
+ [Test]
+ public async Task UserFile_should_expose_download_url_per_spec()
+ {
+ var prop = typeof(UserFile).GetProperty("download_url");
+ await Assert.That(prop).IsNotNull();
+ }
+}
diff --git a/SharpChatwork.Test/src/Query/Spec/IncomingRequestQuerySpecTests.cs b/SharpChatwork.Test/src/Query/Spec/IncomingRequestQuerySpecTests.cs
new file mode 100644
index 0000000..99120b9
--- /dev/null
+++ b/SharpChatwork.Test/src/Query/Spec/IncomingRequestQuerySpecTests.cs
@@ -0,0 +1,21 @@
+using SharpChatwork.Test.Helpers;
+
+namespace SharpChatwork.Test.Query.Spec;
+
+public class IncomingRequestQuerySpecTests
+{
+ [Test]
+ public async Task AcceptAsync_should_use_PUT_per_spec()
+ {
+ var stub = StubChatworkClient.WithJsonResponse(ApiFixtures.IncomingRequestPut);
+ var query = new IncomingRequestQuery(stub);
+
+ await query.AcceptAsync(7);
+
+ await stub.Received(1).QueryAsync(
+ EndPoints.IncomingRequestsOf(7),
+ HttpMethod.Put,
+ Arg.Any(),
+ Arg.Any());
+ }
+}
diff --git a/SharpChatwork.Test/src/Query/Spec/README.md b/SharpChatwork.Test/src/Query/Spec/README.md
new file mode 100644
index 0000000..6325aa3
--- /dev/null
+++ b/SharpChatwork.Test/src/Query/Spec/README.md
@@ -0,0 +1,12 @@
+# Spec Conformance Tests
+
+This folder contains tests that verify the **specification** as documented in `docs/apis/` —
+not the current implementation. Each test name and `[Skip]` reason cites the doc that defines
+the expected behavior.
+
+Tests marked `[Skip("impl-bug: ...")]` describe a known divergence between the implementation
+and the documented Chatwork API. They will start passing automatically once the underlying
+implementation bug is fixed; this is intentional.
+
+Existing tests in `Query/*.cs` lock the **current** behavior of the implementation, so a fix
+to one of these bugs will require updating the matching regression test.
diff --git a/SharpChatwork.Test/src/Query/Spec/RoomFileQuerySpecTests.cs b/SharpChatwork.Test/src/Query/Spec/RoomFileQuerySpecTests.cs
new file mode 100644
index 0000000..a80259b
--- /dev/null
+++ b/SharpChatwork.Test/src/Query/Spec/RoomFileQuerySpecTests.cs
@@ -0,0 +1,30 @@
+using SharpChatwork.Test.Helpers;
+
+namespace SharpChatwork.Test.Query.Spec;
+
+public class RoomFileQuerySpecTests
+{
+ [Test]
+ public async Task EndPoints_RoomFilesOf_should_not_contain_extra_whitespace_per_spec()
+ {
+ var uri = EndPoints.RoomFilesOf(42, 7).ToString();
+
+ await Assert.That(uri).IsEqualTo("https://api.chatwork.com/v2/rooms/42/files/7");
+ }
+
+ [Test]
+ public async Task GetAsync_should_target_file_of_id_path_per_spec()
+ {
+ var stub = StubChatworkClient.WithJsonResponse(ApiFixtures.RoomFileGet);
+ var query = new RoomFileQuery(stub);
+
+ await query.GetAsync(42, 7, createDownloadLink: true);
+
+ await stub.Received(1).QueryAsync(
+ Arg.Is(u => u.OriginalString.StartsWith(
+ "https://api.chatwork.com/v2/rooms/42/files/7", StringComparison.Ordinal)),
+ HttpMethod.Get,
+ Arg.Any(),
+ Arg.Any());
+ }
+}
diff --git a/SharpChatwork.Test/src/Query/Spec/RoomInviteQuerySpecTests.cs b/SharpChatwork.Test/src/Query/Spec/RoomInviteQuerySpecTests.cs
new file mode 100644
index 0000000..d999488
--- /dev/null
+++ b/SharpChatwork.Test/src/Query/Spec/RoomInviteQuerySpecTests.cs
@@ -0,0 +1,51 @@
+using SharpChatwork.Test.Helpers;
+
+namespace SharpChatwork.Test.Query.Spec;
+
+public class RoomInviteQuerySpecTests
+{
+ [Test]
+ public async Task GetAsync_should_use_GET_per_spec()
+ {
+ var stub = StubChatworkClient.WithJsonResponse(ApiFixtures.InviteLinkGet);
+ var query = new RoomInviteQuery(stub);
+
+ await query.GetAsync(42);
+
+ await stub.Received(1).QueryAsync(
+ EndPoints.RoomLink(42),
+ HttpMethod.Get,
+ Arg.Any(),
+ Arg.Any());
+ }
+
+ [Test]
+ public async Task CreateAsync_should_target_link_endpoint_per_spec()
+ {
+ var stub = StubChatworkClient.WithJsonResponse(ApiFixtures.InviteLinkGet);
+ var query = new RoomInviteQuery(stub);
+
+ await query.CreateAsync(42, "code123", "Welcome", requireAcceptance: true);
+
+ await stub.Received(1).QueryAsync(
+ EndPoints.RoomLink(42),
+ HttpMethod.Post,
+ Arg.Any(),
+ Arg.Any());
+ }
+
+ [Test]
+ public async Task UpdateAsync_should_target_link_endpoint_per_spec()
+ {
+ var stub = StubChatworkClient.WithJsonResponse(ApiFixtures.InviteLinkGet);
+ var query = new RoomInviteQuery(stub);
+
+ await query.UpdateAsync(42, "code456", "Hi", requireAcceptance: false);
+
+ await stub.Received(1).QueryAsync(
+ EndPoints.RoomLink(42),
+ HttpMethod.Put,
+ Arg.Any(),
+ Arg.Any());
+ }
+}
diff --git a/SharpChatwork.Test/src/Query/Spec/RoomMemberQuerySpecTests.cs b/SharpChatwork.Test/src/Query/Spec/RoomMemberQuerySpecTests.cs
new file mode 100644
index 0000000..9df0da5
--- /dev/null
+++ b/SharpChatwork.Test/src/Query/Spec/RoomMemberQuerySpecTests.cs
@@ -0,0 +1,25 @@
+using SharpChatwork.Test.Helpers;
+
+namespace SharpChatwork.Test.Query.Spec;
+
+public class RoomMemberQuerySpecTests
+{
+ [Test]
+ public async Task UpdateAsync_should_PUT_members_with_form_data_per_spec()
+ {
+ var stub = StubChatworkClient.WithJsonResponse("""{"admin":[1],"member":[2],"readonly":[3]}""");
+ var query = new RoomMemberQuery(stub);
+
+ await query.UpdateAsync(42, [1L], [2L], [3L]);
+
+ await stub.Received(1).QueryAsync(
+ EndPoints.RoomMember(42),
+ HttpMethod.Put,
+ Arg.Any(),
+ Arg.Any());
+ await StubChatworkClient.AssertFormDataAsync(stub,
+ ("members_admin_ids", "1"),
+ ("members_member_ids", "2"),
+ ("members_readonly_ids", "3"));
+ }
+}
diff --git a/SharpChatwork.Test/src/Query/Spec/RoomMessageQuerySpecTests.cs b/SharpChatwork.Test/src/Query/Spec/RoomMessageQuerySpecTests.cs
new file mode 100644
index 0000000..5ab2579
--- /dev/null
+++ b/SharpChatwork.Test/src/Query/Spec/RoomMessageQuerySpecTests.cs
@@ -0,0 +1,54 @@
+using SharpChatwork.Test.Helpers;
+
+namespace SharpChatwork.Test.Query.Spec;
+
+public class RoomMessageQuerySpecTests
+{
+ [Test]
+ public async Task ReadAsync_should_PUT_messages_read_with_message_id_body_per_spec()
+ {
+ var stub = StubChatworkClient.WithJsonResponse(ApiFixtures.MessageReadUnreadResult);
+ var query = new RoomMessageQuery(stub);
+
+ await query.ReadAsync(42, 5);
+
+ await stub.Received(1).QueryAsync(
+ Arg.Is(u => u.OriginalString == "https://api.chatwork.com/v2/rooms/42/messages/read"),
+ HttpMethod.Put,
+ Arg.Any(),
+ Arg.Any());
+ await StubChatworkClient.AssertFormDataAsync(stub, ("message_id", "5"));
+ }
+
+ [Test]
+ public async Task UnReadAsync_should_PUT_messages_unread_with_message_id_body_per_spec()
+ {
+ var stub = StubChatworkClient.WithJsonResponse(ApiFixtures.MessageReadUnreadResult);
+ var query = new RoomMessageQuery(stub);
+
+ await query.UnReadAsync(42, 5);
+
+ await stub.Received(1).QueryAsync(
+ Arg.Is(u => u.OriginalString == "https://api.chatwork.com/v2/rooms/42/messages/unread"),
+ HttpMethod.Put,
+ Arg.Any(),
+ Arg.Any());
+ await StubChatworkClient.AssertFormDataAsync(stub, ("message_id", "5"));
+ }
+
+ [Test]
+ public async Task UpdateAsync_should_PUT_messages_of_id_with_body_in_form_per_spec()
+ {
+ var stub = StubChatworkClient.WithJsonResponse(ApiFixtures.MessageIdResult);
+ var query = new RoomMessageQuery(stub);
+
+ await query.UpdateAsync(42, 5, "updated");
+
+ await stub.Received(1).QueryAsync(
+ EndPoints.RoomMessagesOf(42, 5),
+ HttpMethod.Put,
+ Arg.Any(),
+ Arg.Any());
+ await StubChatworkClient.AssertFormDataAsync(stub, ("body", "updated"));
+ }
+}
diff --git a/SharpChatwork.Test/src/Query/Spec/RoomQuerySpecTests.cs b/SharpChatwork.Test/src/Query/Spec/RoomQuerySpecTests.cs
new file mode 100644
index 0000000..80ca8ce
--- /dev/null
+++ b/SharpChatwork.Test/src/Query/Spec/RoomQuerySpecTests.cs
@@ -0,0 +1,89 @@
+using SharpChatwork.Test.Helpers;
+
+namespace SharpChatwork.Test.Query.Spec;
+
+public class RoomQuerySpecTests
+{
+ [Test]
+ public async Task EndPoints_Rooms_should_be_lowercase_per_spec()
+ {
+ await Assert.That(EndPoints.Rooms.ToString())
+ .IsEqualTo("https://api.chatwork.com/v2/rooms");
+ }
+
+ [Test]
+ public async Task CreateAsync_should_use_POST_per_spec()
+ {
+ var stub = StubChatworkClient.WithJsonResponse(ApiFixtures.RoomIdResult);
+ var query = new RoomQuery(stub);
+
+ await query.CreateAsync();
+
+ await stub.Received(1).QueryAsync(
+ EndPoints.Rooms,
+ HttpMethod.Post,
+ Arg.Any(),
+ Arg.Any());
+ }
+
+ [Test]
+ public async Task UpdateAsync_should_use_PUT_per_spec()
+ {
+ var stub = StubChatworkClient.WithJsonResponse(ApiFixtures.RoomIdResult);
+ var query = new RoomQuery(stub);
+
+ await query.UpdateAsync(123, "new room", "desc", RoomIconPreset.Project);
+
+ await stub.Received(1).QueryAsync(
+ EndPoints.RoomOf(123),
+ HttpMethod.Put,
+ Arg.Any(),
+ Arg.Any());
+ }
+
+ [Test]
+ public async Task UpdateAsync_should_send_distinct_description_per_spec()
+ {
+ var stub = StubChatworkClient.WithJsonResponse(ApiFixtures.RoomIdResult);
+ var query = new RoomQuery(stub);
+
+ await query.UpdateAsync(123, "new room", "a separate description", RoomIconPreset.Project);
+
+ await StubChatworkClient.AssertFormDataAsync(stub,
+ ("name", "new room"),
+ ("description", "a separate description"),
+ ("icon_preset", "project"));
+ }
+
+ [Test]
+ public async Task DeleteAsync_should_DELETE_room_of_id_per_spec()
+ {
+ var stub = StubChatworkClient.WithJsonResponse(ApiFixtures.RoomIdResult);
+ var query = new RoomQuery(stub);
+
+ await query.DeleteAsync(123);
+
+ await stub.Received(1).QueryAsync(
+ EndPoints.RoomOf(123),
+ HttpMethod.Delete,
+ Arg.Any(),
+ Arg.Any());
+ await StubChatworkClient.AssertFormDataAsync(stub, ("action_type", "delete"));
+ }
+
+ [Test]
+ public async Task LeaveAsync_should_DELETE_room_of_id_per_spec()
+ {
+ var stub = StubChatworkClient.WithJsonResponse(ApiFixtures.RoomIdResult);
+ var query = new RoomQuery(stub);
+
+ await query.LeaveAsync(123);
+
+ await stub.Received(1).QueryAsync(
+ EndPoints.RoomOf(123),
+ HttpMethod.Delete,
+ Arg.Any(),
+ Arg.Any());
+ await StubChatworkClient.AssertFormDataAsync(stub, ("action_type", "leave"));
+ }
+}
diff --git a/SharpChatwork.Test/src/Query/Spec/RoomTaskQuerySpecTests.cs b/SharpChatwork.Test/src/Query/Spec/RoomTaskQuerySpecTests.cs
new file mode 100644
index 0000000..f3f1775
--- /dev/null
+++ b/SharpChatwork.Test/src/Query/Spec/RoomTaskQuerySpecTests.cs
@@ -0,0 +1,56 @@
+using SharpChatwork.Test.Helpers;
+
+namespace SharpChatwork.Test.Query.Spec;
+
+public class RoomTaskQuerySpecTests
+{
+ [Test]
+ public async Task GetAllAsync_should_send_filters_as_query_string_per_spec()
+ {
+ var stub = StubChatworkClient.WithJsonResponse(ApiFixtures.RoomTasksGet);
+ var query = new RoomTaskQuery(stub);
+
+ await query.GetAllAsync(42, accountId: 1, autherId: 2, isDone: false);
+
+ await stub.Received(1).QueryAsync(
+ Arg.Is(u =>
+ u.OriginalString.Contains("account_id=1", StringComparison.Ordinal)
+ && u.OriginalString.Contains("assigned_by_account_id=2", StringComparison.Ordinal)
+ && u.OriginalString.Contains("status=open", StringComparison.Ordinal)),
+ HttpMethod.Get,
+ Arg.Any(),
+ Arg.Any());
+ }
+
+ [Test]
+ public async Task UpdateAsync_should_PUT_tasks_of_id_status_with_body_in_form_per_spec()
+ {
+ var stub = StubChatworkClient.WithJsonResponse(ApiFixtures.TaskIdResult);
+ var query = new RoomTaskQuery(stub);
+
+ await query.UpdateAsync(42, 3, TaskStateType.Done);
+
+ await stub.Received(1).QueryAsync(
+ Arg.Is(u => u.OriginalString == "https://api.chatwork.com/v2/rooms/42/tasks/3/status"),
+ HttpMethod.Put,
+ Arg.Any(),
+ Arg.Any());
+ await StubChatworkClient.AssertFormDataAsync(stub, ("body", "done"));
+ }
+
+ [Test]
+ [Skip("interface-gap: IRoomTaskQuery.CreateAsync(roomId, taskText, limit) is missing required to_ids/limit_type params per spec docs/apis/rooms/tasks/post.md. Signature extension needed before this can be implemented.")]
+ public async Task CreateAsync_should_POST_tasks_with_required_body_per_spec()
+ {
+ var stub = StubChatworkClient.WithJsonResponse("""{"task_ids":[123,124]}""");
+ var query = new RoomTaskQuery(stub);
+
+ await query.CreateAsync(42, "buy milk", 1384354799);
+
+ await stub.Received(1).QueryAsync(
+ EndPoints.RoomTasks(42),
+ HttpMethod.Post,
+ Arg.Any(),
+ Arg.Any());
+ }
+}
diff --git a/SharpChatwork.Test/src/UnitTest1.cs b/SharpChatwork.Test/src/UnitTest1.cs
deleted file mode 100644
index ee7cf5d..0000000
--- a/SharpChatwork.Test/src/UnitTest1.cs
+++ /dev/null
@@ -1,11 +0,0 @@
-using SharpChatwork.Query;
-
-namespace SharpChatwork.Test;
-
-public class UnitTest1
-{
- [Fact]
- public void Test1()
- {
- }
-}
diff --git a/SharpChatwork.Test/src/Usings.cs b/SharpChatwork.Test/src/Usings.cs
index c802f44..9e394e6 100644
--- a/SharpChatwork.Test/src/Usings.cs
+++ b/SharpChatwork.Test/src/Usings.cs
@@ -1 +1,16 @@
-global using Xunit;
+global using System;
+global using System.Collections.Generic;
+global using System.Linq;
+global using System.Net;
+global using System.Net.Http;
+global using System.Threading;
+global using System.Threading.Tasks;
+global using NSubstitute;
+global using RichardSzalay.MockHttp;
+global using SharpChatwork;
+global using SharpChatwork.Query;
+global using SharpChatwork.Query.Types;
+global using TUnit.Assertions;
+global using TUnit.Assertions.Conditions;
+global using TUnit.Assertions.Extensions;
+global using TUnit.Core;
diff --git a/SharpChatwork/src/Client/EndPoints.cs b/SharpChatwork/src/Client/EndPoints.cs
index ecc663b..0543fe3 100644
--- a/SharpChatwork/src/Client/EndPoints.cs
+++ b/SharpChatwork/src/Client/EndPoints.cs
@@ -9,7 +9,7 @@ public class EndPoints
public static readonly Uri Me = new Uri(@"https://api.chatwork.com/v2/me");
public static readonly Uri MyStatus = new Uri(@"https://api.chatwork.com/v2/my/status");
public static readonly Uri MyTasks = new Uri(@"https://api.chatwork.com/v2/my/tasks");
- public static readonly Uri Rooms = new Uri(@"https://api.chatwork.com/v2/Rooms");
+ public static readonly Uri Rooms = new Uri(@"https://api.chatwork.com/v2/rooms");
public static readonly Uri Contacts = new Uri(@"https://api.chatwork.com/v2/contacts");
public static readonly Uri IncomingRequests = new Uri(@"https://api.chatwork.com/v2/incoming_requests");
@@ -60,7 +60,12 @@ public static Uri RoomFiles(long roomId)
public static Uri RoomFilesOf(long roomId, long fileId)
{
- return new Uri($@"https://api.chatwork.com/v 2/rooms/{roomId}/files/{fileId}");
+ return new Uri($@"https://api.chatwork.com/v2/rooms/{roomId}/files/{fileId}");
+ }
+
+ public static Uri RoomTasksOfStatus(long roomId, long taskId)
+ {
+ return new Uri($@"https://api.chatwork.com/v2/rooms/{roomId}/tasks/{taskId}/status");
}
public static Uri RoomLink(long roomId)
diff --git a/SharpChatwork/src/Client/Query/IncomingRequestQuery.cs b/SharpChatwork/src/Client/Query/IncomingRequestQuery.cs
index 71a98f2..6cc6a68 100644
--- a/SharpChatwork/src/Client/Query/IncomingRequestQuery.cs
+++ b/SharpChatwork/src/Client/Query/IncomingRequestQuery.cs
@@ -10,7 +10,7 @@ internal sealed class IncomingRequestQuery(IChatworkClient client) : ClientQuery
{
public async ValueTask AcceptAsync(long requestId, CancellationToken token = default)
{
- return await this.chatworkClient.QueryAsync(EndPoints.IncomingRequestsOf(requestId), HttpMethod.Post, new Dictionary(), token);
+ return await this.chatworkClient.QueryAsync(EndPoints.IncomingRequestsOf(requestId), HttpMethod.Put, new Dictionary(), token);
}
public async ValueTask CancelAsync(long requestId, CancellationToken token = default)
diff --git a/SharpChatwork/src/Client/Query/RoomFileQuery.cs b/SharpChatwork/src/Client/Query/RoomFileQuery.cs
index f74e5bc..b59f338 100644
--- a/SharpChatwork/src/Client/Query/RoomFileQuery.cs
+++ b/SharpChatwork/src/Client/Query/RoomFileQuery.cs
@@ -20,7 +20,7 @@ public async ValueTask> GetAllAsync(long roomId, long acco
public async ValueTask GetAsync(long roomId, long fileId, bool createDownloadLink, CancellationToken token = default)
{
- var uri = $"{EndPoints.RoomFiles(roomId)}?create_download_url={UrlArgEncoder.BoolToInt(createDownloadLink)}";
+ var uri = $"{EndPoints.RoomFilesOf(roomId, fileId)}?create_download_url={UrlArgEncoder.BoolToInt(createDownloadLink)}";
return await this.chatworkClient.QueryAsync(new Uri(uri), HttpMethod.Get, new Dictionary(), token);
}
public async ValueTask UploadAsync(long roomId, Stream stream, string filePath, string message, CancellationToken token = default)
diff --git a/SharpChatwork/src/Client/Query/RoomInviteQuery.cs b/SharpChatwork/src/Client/Query/RoomInviteQuery.cs
index 7e00f5f..eecfc09 100644
--- a/SharpChatwork/src/Client/Query/RoomInviteQuery.cs
+++ b/SharpChatwork/src/Client/Query/RoomInviteQuery.cs
@@ -17,7 +17,7 @@ public async ValueTask CreateAsync(long roomId, string uniqueName, s
{"description", description},
{"need_acceptance", UrlArgEncoder.BoolToInt(requireAcceptance).ToString(CultureInfo.InvariantCulture)},
};
- return await this.chatworkClient.QueryAsync(EndPoints.RoomTasks(roomId), HttpMethod.Post, data, token);
+ return await this.chatworkClient.QueryAsync(EndPoints.RoomLink(roomId), HttpMethod.Post, data, token);
}
public async ValueTask DestroyAsync(long roomId, CancellationToken token = default)
@@ -27,7 +27,7 @@ public async ValueTask DestroyAsync(long roomId, CancellationToken token = defau
public async ValueTask GetAsync(long roomId, CancellationToken token = default)
{
- return await this.chatworkClient.QueryAsync(EndPoints.RoomLink(roomId), HttpMethod.Post, new Dictionary(), token);
+ return await this.chatworkClient.QueryAsync(EndPoints.RoomLink(roomId), HttpMethod.Get, new Dictionary(), token);
}
public async ValueTask UpdateAsync(long roomId, string uniqueName, string description, bool requireAcceptance, CancellationToken token = default)
@@ -38,6 +38,6 @@ public async ValueTask UpdateAsync(long roomId, string uniqueName, s
{"description", description},
{"need_acceptance", UrlArgEncoder.BoolToInt(requireAcceptance).ToString(CultureInfo.InvariantCulture)},
};
- return await this.chatworkClient.QueryAsync(EndPoints.RoomTasks(roomId), HttpMethod.Put, data, token);
+ return await this.chatworkClient.QueryAsync(EndPoints.RoomLink(roomId), HttpMethod.Put, data, token);
}
}
diff --git a/SharpChatwork/src/Client/Query/RoomMemberQuery.cs b/SharpChatwork/src/Client/Query/RoomMemberQuery.cs
index 12a4161..e03fe75 100644
--- a/SharpChatwork/src/Client/Query/RoomMemberQuery.cs
+++ b/SharpChatwork/src/Client/Query/RoomMemberQuery.cs
@@ -1,5 +1,6 @@
-using System;
using System.Collections.Generic;
+using System.Globalization;
+using System.Linq;
using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;
@@ -14,18 +15,19 @@ public async ValueTask> GetAllAsync(long roomId, CancellationT
return await this.chatworkClient.QueryAsync>(EndPoints.RoomMember(roomId), HttpMethod.Get, new Dictionary(), token);
}
- public ValueTask UpdateAsync(long roomId, IEnumerable adminsMembers, IEnumerable normalMembers, IEnumerable readonlyMembers, CancellationToken token = default)
+ public async ValueTask UpdateAsync(long roomId, IEnumerable adminsMembers, IEnumerable normalMembers, IEnumerable readonlyMembers, CancellationToken token = default)
{
- throw new NotImplementedException();
- /*
- var data = new Dictionary()
+ var data = new Dictionary
{
- // TODO convert
- // {"members_admin_ids",adminsMembers},
- // {"members_member_ids",roomName },
- // {"members_readonly_ids",preset.ToAliasOrDefault() }
+ {"members_admin_ids", JoinIds(adminsMembers)},
+ {"members_member_ids", JoinIds(normalMembers)},
+ {"members_readonly_ids", JoinIds(readonlyMembers)},
};
- return await this.chatworkClient.QueryAsync(EndPoints.RoomMember(roomId), HttpMethod.Get, data);
- */
+ return await this.chatworkClient.QueryAsync(EndPoints.RoomMember(roomId), HttpMethod.Put, data, token);
+ }
+
+ private static string JoinIds(IEnumerable ids)
+ {
+ return string.Join(",", ids.Select(id => id.ToString(CultureInfo.InvariantCulture)));
}
}
diff --git a/SharpChatwork/src/Client/Query/RoomMessageQuery.cs b/SharpChatwork/src/Client/Query/RoomMessageQuery.cs
index f5e7985..b174648 100644
--- a/SharpChatwork/src/Client/Query/RoomMessageQuery.cs
+++ b/SharpChatwork/src/Client/Query/RoomMessageQuery.cs
@@ -25,8 +25,11 @@ public async ValueTask GetAsync(long roomId, long messageId, Cancel
public async ValueTask ReadAsync(long roomId, long messageId, CancellationToken token = default)
{
- var uri = $"{EndPoints.RoomMessages(roomId)}?message_id={messageId}";
- return await this.chatworkClient.QueryAsync(new Uri(uri), HttpMethod.Post, new Dictionary(), token);
+ var data = new Dictionary
+ {
+ {"message_id", messageId.ToString(CultureInfo.InvariantCulture)},
+ };
+ return await this.chatworkClient.QueryAsync(EndPoints.RoomMessagesRead(roomId), HttpMethod.Put, data, token);
}
public async ValueTask RemoveAsync(long roomId, long messageId, CancellationToken token = default)
@@ -46,13 +49,19 @@ public async ValueTask SendAsync(long roomId, string message, bool is
public async ValueTask UnReadAsync(long roomId, long messageId, CancellationToken token = default)
{
- var uri = $"{EndPoints.RoomMessages(roomId)}?message_id={messageId}";
- return await this.chatworkClient.QueryAsync(new Uri(uri), HttpMethod.Post, new Dictionary(), token);
+ var data = new Dictionary
+ {
+ {"message_id", messageId.ToString(CultureInfo.InvariantCulture)},
+ };
+ return await this.chatworkClient.QueryAsync(EndPoints.RoomMessagesUnread(roomId), HttpMethod.Put, data, token);
}
public async ValueTask UpdateAsync(long roomId, long messageId, string message, CancellationToken token = default)
{
- var uri = $"{EndPoints.RoomMessagesOf(roomId, messageId)}?body={message}";
- return await this.chatworkClient.QueryAsync(new Uri(uri), HttpMethod.Post, new Dictionary(), token);
+ var data = new Dictionary
+ {
+ {"body", message},
+ };
+ return await this.chatworkClient.QueryAsync(EndPoints.RoomMessagesOf(roomId, messageId), HttpMethod.Put, data, token);
}
}
diff --git a/SharpChatwork/src/Client/Query/RoomQuery.cs b/SharpChatwork/src/Client/Query/RoomQuery.cs
index c397d2c..beb04e9 100644
--- a/SharpChatwork/src/Client/Query/RoomQuery.cs
+++ b/SharpChatwork/src/Client/Query/RoomQuery.cs
@@ -17,13 +17,16 @@ internal sealed class RoomQuery(IChatworkClient client) : ClientQuery(client), I
public async ValueTask CreateAsync(CancellationToken token = default)
{
- return await this.chatworkClient.QueryAsync(EndPoints.Rooms, HttpMethod.Get, new Dictionary(), token);
+ return await this.chatworkClient.QueryAsync(EndPoints.Rooms, HttpMethod.Post, new Dictionary(), token);
}
public async ValueTask DeleteAsync(long roomId, CancellationToken token = default)
{
- var uri = $"{EndPoints.RoomMessages(roomId)}?action_type=delete";
- await this.chatworkClient.QueryAsync(new Uri(uri), HttpMethod.Post, new Dictionary(), token);
+ var data = new Dictionary
+ {
+ {"action_type", "delete"},
+ };
+ await this.chatworkClient.QueryAsync(EndPoints.RoomOf(roomId), HttpMethod.Delete, data, token);
}
public async ValueTask> GetAllAsync(CancellationToken token = default)
@@ -38,8 +41,11 @@ public async ValueTask GetAsync(long roomId, CancellationToken token = def
public async ValueTask LeaveAsync(long roomId, CancellationToken token = default)
{
- var uri = $"{EndPoints.RoomMessages(roomId)}?action_type=leave";
- await this.chatworkClient.QueryAsync(new Uri(uri), HttpMethod.Post, new Dictionary(), token);
+ var data = new Dictionary
+ {
+ {"action_type", "leave"},
+ };
+ await this.chatworkClient.QueryAsync(EndPoints.RoomOf(roomId), HttpMethod.Delete, data, token);
}
public async ValueTask UpdateAsync(
@@ -49,16 +55,10 @@ public async ValueTask UpdateAsync(
{
var data = new Dictionary
{
- {
- "name", roomName
- },
- {
- "description", roomName
- },
- {
- "icon_preset", preset.ToAliasOrDefault()
- },
+ {"name", roomName},
+ {"description", description},
+ {"icon_preset", preset.ToAliasOrDefault()},
};
- return await this.chatworkClient.QueryAsync(EndPoints.RoomOf(roomId), HttpMethod.Post, data, token);
+ return await this.chatworkClient.QueryAsync(EndPoints.RoomOf(roomId), HttpMethod.Put, data, token);
}
}
diff --git a/SharpChatwork/src/Client/Query/RoomTaskQuery.cs b/SharpChatwork/src/Client/Query/RoomTaskQuery.cs
index 19fe54a..6147353 100644
--- a/SharpChatwork/src/Client/Query/RoomTaskQuery.cs
+++ b/SharpChatwork/src/Client/Query/RoomTaskQuery.cs
@@ -30,21 +30,20 @@ public async ValueTask GetAsync(long roomId, long taskId, Cancellation
public async ValueTask> GetAllAsync(long roomId, long accountId, long autherId, bool isDone = false, CancellationToken token = default)
{
- var doneString = "done";
- if(!isDone)
- doneString = "open";
- var data = new Dictionary
- {
- {"account_id", accountId.ToString(CultureInfo.InvariantCulture)},
- {"assigned_by_account_id", autherId.ToString(CultureInfo.InvariantCulture)},
- {"status", doneString},
- };
- return await this.chatworkClient.QueryAsync>(EndPoints.RoomTasks(roomId), HttpMethod.Get, data, token);
+ var doneString = isDone ? "done" : "open";
+ var uri = $"{EndPoints.RoomTasks(roomId)}"
+ + $"?account_id={accountId.ToString(CultureInfo.InvariantCulture)}"
+ + $"&assigned_by_account_id={autherId.ToString(CultureInfo.InvariantCulture)}"
+ + $"&status={doneString}";
+ return await this.chatworkClient.QueryAsync>(new Uri(uri), HttpMethod.Get, new Dictionary(), token);
}
public async ValueTask UpdateAsync(long roomId, long taskId, TaskStateType state, CancellationToken token = default)
{
- var uri = $"{EndPoints.RoomTasksOf(roomId, taskId)}?body={state.ToAliasOrDefault()}";
- return await this.chatworkClient.QueryAsync(new Uri(uri), HttpMethod.Post, new Dictionary(), token);
+ var data = new Dictionary
+ {
+ {"body", state.ToAliasOrDefault()},
+ };
+ return await this.chatworkClient.QueryAsync(EndPoints.RoomTasksOfStatus(roomId, taskId), HttpMethod.Put, data, token);
}
}
diff --git a/SharpChatwork/src/Client/QueryResult/Room.cs b/SharpChatwork/src/Client/QueryResult/Room.cs
index a3a9ea6..cc5cf60 100644
--- a/SharpChatwork/src/Client/QueryResult/Room.cs
+++ b/SharpChatwork/src/Client/QueryResult/Room.cs
@@ -18,4 +18,5 @@ public class Room
public int task_num { get; set; } = 0;
public string icon_path { get; set; } = string.Empty;
public int last_update_time { get; set; } = 0;
+ public string description { get; set; } = string.Empty;
}
diff --git a/SharpChatwork/src/Client/QueryResult/UserFile.cs b/SharpChatwork/src/Client/QueryResult/UserFile.cs
index db124aa..47e33a4 100644
--- a/SharpChatwork/src/Client/QueryResult/UserFile.cs
+++ b/SharpChatwork/src/Client/QueryResult/UserFile.cs
@@ -10,4 +10,5 @@ public class UserFile
public string filename { get; set; }
public int filesize { get; set; }
public int upload_time { get; set; }
+ public string download_url { get; set; }
}
diff --git a/SharpChatwork/src/Client/QueryResult/UserTask.cs b/SharpChatwork/src/Client/QueryResult/UserTask.cs
index 37af8ef..ee932f7 100644
--- a/SharpChatwork/src/Client/QueryResult/UserTask.cs
+++ b/SharpChatwork/src/Client/QueryResult/UserTask.cs
@@ -6,6 +6,7 @@ public class UserTask
{
public int task_id { get; set; }
public Room room { get; set; }
+ public User account { get; set; }
public User assigned_by_account { get; set; }
public string message_id { get; set; }
public string body { get; set; }
diff --git a/docs/NEXT_RELEASE_NOTE.md b/docs/NEXT_RELEASE_NOTE.md
index e69de29..658fa7a 100644
--- a/docs/NEXT_RELEASE_NOTE.md
+++ b/docs/NEXT_RELEASE_NOTE.md
@@ -0,0 +1,34 @@
+## v0.3.0
+
+#### EndPoints
+- `EndPoints.Rooms` の URL `/Rooms` (大文字) を `/rooms` に修正
+- `EndPoints.RoomFilesOf` の URL に混入していた連続空白 (`v 2`) を `v2` に修正
+- `EndPoints.RoomTasksOfStatus(roomId, taskId)` を新規追加 (PUT `/rooms/{id}/tasks/{tid}/status` 用)
+
+#### RoomQuery
+- `CreateAsync`: GET → POST に修正
+- `UpdateAsync`: POST → PUT に修正、`description` form 値に `roomName` を入れていたバグを修正
+- `DeleteAsync` / `LeaveAsync`: 誤った POST `/rooms/{id}/messages?action_type=...` → 正しい DELETE `/rooms/{id}` + `action_type` form body に修正
+
+#### RoomMessageQuery
+- `ReadAsync`: POST `/rooms/{id}/messages?message_id=...` → PUT `/rooms/{id}/messages/read` + `message_id` form body
+- `UnReadAsync`: POST `/rooms/{id}/messages?message_id=...` → PUT `/rooms/{id}/messages/unread` + `message_id` form body
+- `UpdateAsync`: POST `/rooms/{id}/messages/{mid}?body=...` → PUT `/rooms/{id}/messages/{mid}` + `body` form body
+
+#### RoomTaskQuery
+- `GetAllAsync`: フィルタ (`account_id` / `assigned_by_account_id` / `status`) を form body → query string へ移動
+- `UpdateAsync`: POST `/rooms/{id}/tasks/{tid}?body=...` → PUT `/rooms/{id}/tasks/{tid}/status` + `body` form body
+
+#### RoomFileQuery
+- `GetAsync`: URL に `file_id` が含まれていなかったバグを修正 (`EndPoints.RoomFiles(roomId)` → `EndPoints.RoomFilesOf(roomId, fileId)`)
+
+#### RoomInviteQuery
+- `GetAsync`: POST → GET に修正
+- `CreateAsync`: 誤って `EndPoints.RoomTasks(id)` を叩いていた → `EndPoints.RoomLink(id)` に修正
+- `UpdateAsync`: 同上、`EndPoints.RoomTasks(id)` → `EndPoints.RoomLink(id)`
+
+#### IncomingRequestQuery
+- `AcceptAsync`: POST → PUT に修正
+
+#### RoomMemberQuery
+- `UpdateAsync`: `NotImplementedException` → PUT `/rooms/{id}/members` + `members_admin_ids` / `members_member_ids` / `members_readonly_ids` (long のカンマ区切り) form body を実装
diff --git a/global.json b/global.json
index f46882a..fddfa0d 100644
--- a/global.json
+++ b/global.json
@@ -2,5 +2,8 @@
"sdk": {
"version": "10.0.200",
"rollForward": "latestFeature"
+ },
+ "test": {
+ "runner": "Microsoft.Testing.Platform"
}
}
diff --git a/scripts/test.sh b/scripts/test.sh
new file mode 100644
index 0000000..9c9734c
--- /dev/null
+++ b/scripts/test.sh
@@ -0,0 +1,12 @@
+#!/bin/bash -xe
+
+cd `dirname $0` && cd ../
+
+COVERAGE_FILE="coverage.cobertura.xml"
+REPORT_DIR="coverage-report"
+
+dotnet tool restore
+dotnet test --coverage --coverage-output-format cobertura --coverage-output ${COVERAGE_FILE}
+dotnet reportgenerator "-reports:**/TestResults/${COVERAGE_FILE}" "-targetdir:${REPORT_DIR}" "-reporttypes:Html;Badges;TextSummary"
+
+cat ${REPORT_DIR}/Summary.txt