From a9f42aa0cb9311d47e53043077850904cf4b21b2 Mon Sep 17 00:00:00 2001 From: Aditya Agarwal Date: Wed, 20 Aug 2025 17:34:18 +0200 Subject: [PATCH 1/3] chore: update test/docs --- tests/FeedIntegrationTests.cs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/tests/FeedIntegrationTests.cs b/tests/FeedIntegrationTests.cs index a05bbd5..ac2005d 100644 --- a/tests/FeedIntegrationTests.cs +++ b/tests/FeedIntegrationTests.cs @@ -44,6 +44,7 @@ public async Task SetUp() var solutionRoot = Path.GetFullPath(Path.Combine(Directory.GetCurrentDirectory(), "..")); var envFilePath = Path.Combine(solutionRoot, ".env"); + // snippet-start: Getting_Started ClientBuilder builder; if (File.Exists(envFilePath)) { @@ -53,7 +54,9 @@ public async Task SetUp() { builder = ClientBuilder.FromEnv(); } - + // builder = new ClientBuilder().ApiKey("").ApiSecret(""); + // builder.Build(); + // snippet-end: Getting_Started _client = builder.Build(); _feedsV3Client = builder.BuildFeedsClient(); From 8f60f2bc05099e3881b7e0705a256efe5f1984f6 Mon Sep 17 00:00:00 2001 From: Aditya Agarwal Date: Wed, 20 Aug 2025 17:56:55 +0200 Subject: [PATCH 2/3] chore: add tests --- tests/FeedIntegrationTests.cs | 305 ++++++++++++++++++++++++++++------ 1 file changed, 258 insertions(+), 47 deletions(-) diff --git a/tests/FeedIntegrationTests.cs b/tests/FeedIntegrationTests.cs index ac2005d..9d80323 100644 --- a/tests/FeedIntegrationTests.cs +++ b/tests/FeedIntegrationTests.cs @@ -54,9 +54,9 @@ public async Task SetUp() { builder = ClientBuilder.FromEnv(); } - // builder = new ClientBuilder().ApiKey("").ApiSecret(""); - // builder.Build(); + // snippet-end: Getting_Started + _client = builder.Build(); _feedsV3Client = builder.BuildFeedsClient(); @@ -180,7 +180,7 @@ public void Test01_SetupEnvironmentDemo() Console.WriteLine($" Test Feed 1: {_testFeedId}"); Console.WriteLine($" Test Feed 2: {_testFeedId2}"); - Assert.That(true, Is.True); // Just a demo test + Assert.Pass(); // Just a demo test } // ================================================================= @@ -255,6 +255,114 @@ public async Task Test02b_CreateActivityWithAttachments() } [Test, Order(4)] + public async Task Test02c_CreateVideoActivity() + { + Console.WriteLine("\nšŸŽ„ Testing video activity creation..."); + + // snippet-start: AddVideoActivity + var activity = new AddActivityRequest + { + Type = "video", + Text = "Check out this amazing video!", + UserID = _testUserId, + Feeds = new List { $"user:{_testFeedId}" } + // Note: Video attachments would be added here in a real scenario + }; + var response = await _feedsV3Client.AddActivityAsync(activity); + // snippet-end: AddVideoActivity + + Assert.That(response, Is.Not.Null); + Assert.That(response.Data, Is.Not.Null); + Assert.That(response.Data!.Activity, Is.Not.Null); + + var activityId = response.Data!.Activity!.ID!; + _createdActivityIds.Add(activityId); + + Console.WriteLine($"āœ… Created video activity: {activityId}"); + } + + [Test, Order(5)] + public async Task Test02d_CreateStoryActivityWithExpiration() + { + Console.WriteLine("\nšŸ“– Testing story activity with expiration..."); + + // snippet-start: AddStoryActivityWithExpiration + var tomorrow = DateTime.UtcNow.AddDays(1); + var activity = new AddActivityRequest + { + Type = "story", + Text = "My daily story - expires tomorrow!", + UserID = _testUserId, + Feeds = new List { $"user:{_testFeedId}" }, + ExpiresAt = tomorrow.ToString("yyyy-MM-ddTHH:mm:ssZ"), + Attachments = new List + { + new Attachment + { + ImageUrl = "https://example.com/story-image.jpg", + Type = "image" + }, + new Attachment + { + AssetUrl = "https://example.com/story-video.mp4", + Type = "video" + } + }, + Custom = new Dictionary + { + ["story_type"] = "daily", + ["auto_expire"] = true + } + }; + var response = await _feedsV3Client.AddActivityAsync(activity); + // snippet-end: AddStoryActivityWithExpiration + + Assert.That(response, Is.Not.Null); + Assert.That(response.Data, Is.Not.Null); + Assert.That(response.Data!.Activity, Is.Not.Null); + + var activityId = response.Data!.Activity!.ID!; + _createdActivityIds.Add(activityId); + + Console.WriteLine($"āœ… Created story activity with expiration: {activityId}"); + } + + [Test, Order(6)] + public async Task Test02e_CreateActivityMultipleFeeds() + { + Console.WriteLine("\nšŸ“” Testing activity creation to multiple feeds..."); + + // snippet-start: AddActivityToMultipleFeeds + var activity = new AddActivityRequest + { + Type = "post", + Text = "This post appears in multiple feeds!", + UserID = _testUserId, + Feeds = new List + { + $"user:{_testFeedId}", + $"user:{_testFeedId2}" + }, + Custom = new Dictionary + { + ["cross_posted"] = true, + ["target_feeds"] = 2 + } + }; + var response = await _feedsV3Client.AddActivityAsync(activity); + // snippet-end: AddActivityToMultipleFeeds + + Assert.That(response, Is.Not.Null); + Assert.That(response.Data, Is.Not.Null); + Assert.That(response.Data!.Activity, Is.Not.Null); + + var activityId = response.Data!.Activity!.ID!; + _createdActivityIds.Add(activityId); + + Console.WriteLine($"āœ… Created activity in multiple feeds: {activityId}"); + } + + [Test, Order(7)] public async Task Test03_QueryActivities() { Console.WriteLine("\nšŸ” Testing activity querying..."); @@ -275,7 +383,7 @@ public async Task Test03_QueryActivities() Console.WriteLine("āœ… Queried activities successfully"); } - [Test, Order(5)] + [Test, Order(8)] public async Task Test04_GetSingleActivity() { Console.WriteLine("\nšŸ“„ Testing single activity retrieval..."); @@ -306,7 +414,7 @@ public async Task Test04_GetSingleActivity() Console.WriteLine("āœ… Retrieved single activity"); } - [Test, Order(6)] + [Test, Order(9)] public async Task Test05_UpdateActivity() { Console.WriteLine("\nāœļø Testing activity update..."); @@ -689,7 +797,7 @@ public async Task Test14_FollowUser() { Console.WriteLine("\nšŸ‘„ Testing follow operation..."); - // snippet-start: FollowUser + // snippet-start: Follow var response = await _feedsV3Client.FollowAsync( new FollowRequest { @@ -697,7 +805,7 @@ public async Task Test14_FollowUser() Target = $"user:{_testFeedId2}" } ); - // snippet-end: FollowUser + // snippet-end: Follow Assert.That(response, Is.Not.Null); Console.WriteLine("āœ… Followed user"); @@ -926,13 +1034,13 @@ await _feedsV3Client.AddReactionAsync( } ); - // snippet-start: DeleteReaction + // snippet-start: DeleteActivityReaction var response = await _feedsV3Client.DeleteActivityReactionAsync( activityId, "like", new { user_id = _testUserId } ); - // snippet-end: DeleteReaction + // snippet-end: DeleteActivityReaction Assert.That(response, Is.Not.Null); Console.WriteLine("āœ… Deleted reaction"); @@ -996,13 +1104,13 @@ await _feedsV3Client.FollowAsync( } ); - // snippet-start: UnfollowUser + // snippet-start: Unfollow var response = await _feedsV3Client.UnfollowAsync( $"user:{_testFeedId}", $"user:{_testFeedId3}", new { user_id = _testUserId } ); - // snippet-end: UnfollowUser + // snippet-end: Unfollow Assert.That(response, Is.Not.Null); Console.WriteLine("āœ… Unfollowed user"); @@ -1151,20 +1259,38 @@ public async Task Test27_DeviceManagement() { Console.WriteLine("\nšŸ“± Testing device management..."); - // snippet-start: DeviceManagement - // Note: Device management typically requires specific device tokens - // This test demonstrates the concept - var deviceId = $"test-device-{Guid.NewGuid()}"; - Console.WriteLine($"Managing device: {deviceId}"); + var deviceToken = $"test-device-token-{Guid.NewGuid()}"; + + try + { + // snippet-start: AddDevice + var addDeviceResponse = await _client.CreateDeviceAsync( + new CreateDeviceRequest + { + ID = deviceToken, + PushProvider = "apn", + UserID = _testUserId + } + ); + // snippet-end: AddDevice + + Assert.That(addDeviceResponse, Is.Not.Null); + Console.WriteLine($"āœ… Added device: {deviceToken}"); - // In a real scenario, you would: - // 1. Register device tokens - // 2. Associate devices with users - // 3. Send push notifications - // 4. Manage device preferences - // snippet-end: DeviceManagement - - Console.WriteLine("āœ… Device management test completed (demo only)"); + // snippet-start: RemoveDevice + var removeDeviceResponse = await _client.DeleteDeviceAsync( + deviceToken + ); + // snippet-end: RemoveDevice + + Assert.That(removeDeviceResponse, Is.Not.Null); + Console.WriteLine($"āœ… Removed device: {deviceToken}"); + } + catch (Exception ex) + { + Console.WriteLine($"Device management skipped: {ex.Message}"); + // Device management might not be available in all configurations + } } [Test, Order(26)] @@ -1182,7 +1308,13 @@ public async Task Test28_QueryActivitiesWithFilters() Type = type, Text = $"Test {type} activity for filtering", UserID = _testUserId, - Feeds = new List { $"user:{_testFeedId}" } + Feeds = new List { $"user:{_testFeedId}" }, + Custom = new Dictionary + { + ["category"] = type, + ["priority"] = new Random().Next(1, 6), + ["tags"] = new[] { type, "test", "filter" } + } }; var response = await _feedsV3Client.AddActivityAsync(activity); @@ -1192,22 +1324,42 @@ public async Task Test28_QueryActivitiesWithFilters() } } - // snippet-start: QueryActivitiesWithFilters - var filterResponse = await _feedsV3Client.QueryActivitiesAsync( + // Query with type filter + // snippet-start: QueryActivitiesWithTypeFilter + var typeFilterResponse = await _feedsV3Client.QueryActivitiesAsync( new QueryActivitiesRequest { - Filter = new Dictionary - { + Limit = 10, + Filter = new Dictionary + { ["activity_type"] = "post", ["user_id"] = _testUserId - }, - Limit = 5 + } + // Note: Sort would be added here: Sort = new Dictionary { ["created_at"] = -1 } } ); - // snippet-end: QueryActivitiesWithFilters + // snippet-end: QueryActivitiesWithTypeFilter - Assert.That(filterResponse, Is.Not.Null); + Assert.That(typeFilterResponse, Is.Not.Null); Console.WriteLine("āœ… Queried activities with type filter"); + + // Query with custom field filter + // snippet-start: QueryActivitiesWithCustomFilter + var customFilterResponse = await _feedsV3Client.QueryActivitiesAsync( + new QueryActivitiesRequest + { + Limit = 10, + Filter = new Dictionary + { + // Note: Custom field filtering syntax may vary by API implementation + ["user_id"] = _testUserId + } + } + ); + // snippet-end: QueryActivitiesWithCustomFilter + + Assert.That(customFilterResponse, Is.Not.Null); + Console.WriteLine("āœ… Queried activities with custom filter"); } [Test, Order(27)] @@ -1228,6 +1380,7 @@ public async Task Test29_GetFeedActivitiesWithPagination() Assert.That(firstPageResponse, Is.Not.Null); // Get second page if there's a next token + // snippet-start: GetFeedActivitiesSecondPage if (firstPageResponse.Data?.Next != null) { var secondPageResponse = await _feedsV3Client.QueryActivitiesAsync( @@ -1242,6 +1395,11 @@ public async Task Test29_GetFeedActivitiesWithPagination() Assert.That(secondPageResponse, Is.Not.Null); Console.WriteLine("āœ… Retrieved second page of activities"); } + else + { + Console.WriteLine("āš ļø No next page available"); + } + // snippet-end: GetFeedActivitiesSecondPage // snippet-end: GetFeedActivitiesWithPagination Console.WriteLine("āœ… Retrieved feed activities with pagination"); @@ -1252,12 +1410,12 @@ public async Task Test30_ErrorHandlingScenarios() { Console.WriteLine("\nāš ļø Testing error handling scenarios..."); + // Test 1: Invalid activity ID try { - // snippet-start: ErrorHandling - // Try to get a non-existent activity - await _feedsV3Client.GetActivityAsync("non-existent-activity-id"); - // snippet-end: ErrorHandling + // snippet-start: HandleInvalidActivityId + await _feedsV3Client.GetActivityAsync("invalid-activity-id-12345"); + // snippet-end: HandleInvalidActivityId Assert.Fail("Expected exception was not thrown"); } @@ -1271,6 +1429,57 @@ public async Task Test30_ErrorHandlingScenarios() Assert.That(ex, Is.Not.Null); Console.WriteLine($"āœ… Caught expected error for invalid activity ID: {ex.GetType().Name}"); } + + // Test 2: Empty activity text + try + { + // snippet-start: HandleEmptyActivityText + var emptyActivity = new AddActivityRequest + { + Type = "post", + Text = "", // Empty text + UserID = _testUserId, + Feeds = new List { $"user:{_testFeedId}" } + }; + await _feedsV3Client.AddActivityAsync(emptyActivity); + // snippet-end: HandleEmptyActivityText + + // If we reach here, the API allows empty text (which might be valid) + Console.WriteLine("āœ… Empty activity text was accepted by API"); + } + catch (GetStreamApiException ex) + { + Console.WriteLine($"āœ… Caught expected error for empty activity text: {ex.GetType().Name}"); + } + catch (Exception ex) + { + Console.WriteLine($"āœ… Caught expected error for empty activity text: {ex.GetType().Name}"); + } + + // Test 3: Invalid user ID + try + { + // snippet-start: HandleInvalidUserId + var invalidUserActivity = new AddActivityRequest + { + Type = "post", + Text = "Test with invalid user", + UserID = "", // Empty user ID + Feeds = new List { $"user:{_testFeedId}" } + }; + await _feedsV3Client.AddActivityAsync(invalidUserActivity); + // snippet-end: HandleInvalidUserId + + Assert.Fail("Expected exception was not thrown for invalid user ID"); + } + catch (GetStreamApiException ex) + { + Console.WriteLine($"āœ… Caught expected error for invalid user ID: {ex.GetType().Name}"); + } + catch (Exception ex) + { + Console.WriteLine($"āœ… Caught expected error for invalid user ID: {ex.GetType().Name}"); + } } [Test, Order(29)] @@ -1278,36 +1487,38 @@ public async Task Test31_AuthenticationScenarios() { Console.WriteLine("\nšŸ” Testing authentication scenarios..."); - // snippet-start: AuthenticationScenarios - // Test that our client is properly authenticated + // Test with valid user authentication + // snippet-start: ValidUserAuthentication var activity = new AddActivityRequest { - Type = "auth_test", - Text = "Testing authentication", + Type = "post", + Text = "Activity with proper authentication", UserID = _testUserId, Feeds = new List { $"user:{_testFeedId}" } }; - var response = await _feedsV3Client.AddActivityAsync(activity); + // snippet-end: ValidUserAuthentication + Assert.That(response.Data?.Activity?.ID, Is.Not.Null); var activityId = response.Data!.Activity!.ID!; _createdActivityIds.Add(activityId); Console.WriteLine($"āœ… Successfully authenticated and created activity: {activityId}"); - // Test updating with proper user permissions + // Test user permissions for updating activity + // snippet-start: UserPermissionUpdate var updateResponse = await _feedsV3Client.UpdateActivityAsync( activityId, new UpdateActivityRequest { - Text = "Updated with proper authentication", - UserID = _testUserId + Text = "Updated with proper user permissions", + UserID = _testUserId // Same user can update } ); + // snippet-end: UserPermissionUpdate Assert.That(updateResponse, Is.Not.Null); Console.WriteLine("āœ… Successfully updated activity with proper user permissions"); - // snippet-end: AuthenticationScenarios } // ================================================================= @@ -1348,7 +1559,7 @@ public async Task Test32_RealWorldUsageDemo() var postResponse = await _feedsV3Client.AddActivityAsync(postActivity); Assert.That(postResponse.Data?.Activity?.ID, Is.Not.Null); - var postId = postResponse.Data.Activity.ID; + var postId = postResponse.Data!.Activity!.ID!; _createdActivityIds.Add(postId); // 2. Other users react to the post From 5b7fa6fe81d24fd7de772a7495fe996c04be334b Mon Sep 17 00:00:00 2001 From: Aditya Agarwal Date: Fri, 22 Aug 2025 20:09:00 +0200 Subject: [PATCH 3/3] chore: update creds --- .github/workflows/create-release-pr.yml | 2 +- .github/workflows/release.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/create-release-pr.yml b/.github/workflows/create-release-pr.yml index 5fea109..3251a8f 100644 --- a/.github/workflows/create-release-pr.yml +++ b/.github/workflows/create-release-pr.yml @@ -106,7 +106,7 @@ jobs: - name: Run tests to ensure everything works env: - STREAM_API_KEY: ${{ secrets.STREAM_API_KEY }} + STREAM_API_KEY: ${{ vars.STREAM_API_KEY }} STREAM_API_SECRET: ${{ secrets.STREAM_API_SECRET }} CONFIGURATION: Release run: | diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index aa7f0f6..84449e7 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -101,7 +101,7 @@ jobs: - name: Test env: - STREAM_API_KEY: ${{ secrets.STREAM_API_KEY }} + STREAM_API_KEY: ${{ vars.STREAM_API_KEY }} STREAM_API_SECRET: ${{ secrets.STREAM_API_SECRET }} CONFIGURATION: Release run: make test