|
14 | 14 | using Refresh.Interfaces.Game.Types.Levels; |
15 | 15 | using System.Security.Cryptography; |
16 | 16 | using System.Text; |
| 17 | +using System.Net; |
17 | 18 |
|
18 | 19 | namespace RefreshTests.GameServer.Tests.Levels; |
19 | 20 |
|
@@ -496,113 +497,91 @@ public void CantUnpublishSomeoneElsesLevel() |
496 | 497 | } |
497 | 498 |
|
498 | 499 | [Test] |
499 | | - [TestCase(2, 2)] |
500 | | - public void CantPublishAfterExceedingTimedLevelLimit(int levelQuota, int uploadAttemptsAfterExceeding) |
| 500 | + [TestCase(4, 4, 1)] |
| 501 | + public void LevelUploadsGetRateLimitedTemporarily(int levelQuota, int uploadAttemptsAfterExceeding, int timeSpanHours) |
501 | 502 | { |
502 | 503 | using TestContext context = this.GetServer(); |
503 | 504 | GameUser user = context.CreateUser("thepublisher"); |
504 | 505 |
|
505 | 506 | // Prepare config |
506 | 507 | GameServerConfig config = context.Server.Value.GameServerConfig; |
507 | | - EntityUploadRateLimitProperties timedLevelLimit = new() |
| 508 | + EntityUploadRateLimitProperties uploadConfig = new() |
508 | 509 | { |
509 | 510 | Enabled = true, |
510 | 511 | UploadQuota = levelQuota, |
511 | | - TimeSpanHours = 1, |
| 512 | + TimeSpanHours = timeSpanHours, |
512 | 513 | }; |
513 | | - config.NormalUserPermissions.LevelUploadRateLimit = timedLevelLimit; |
| 514 | + config.NormalUserPermissions.LevelUploadRateLimit = uploadConfig; |
514 | 515 |
|
515 | 516 | using HttpClient client = context.GetAuthenticatedClient(TokenType.Game, user); |
| 517 | + int publishAttempts = 0; |
516 | 518 |
|
517 | | - // Upload level asset |
518 | | - HttpResponseMessage assetUploadMessage = client.PostAsync($"/lbp/upload/{TEST_ASSET_HASH}", new ReadOnlyMemoryContent("LVLb"u8.ToArray())).Result; |
519 | | - Assert.That(assetUploadMessage.StatusCode, Is.EqualTo(OK)); |
| 519 | + // Not blocked yet |
| 520 | + Assert.That(context.Database.GetUploadRateLimit(user, GameDatabaseEntity.Level), Is.Null); |
| 521 | + Assert.That(context.Database.GetRemainingTimeIfUploadRateLimitReached(user, GameDatabaseEntity.Level, uploadConfig.UploadQuota), Is.Null); |
520 | 522 |
|
521 | | - // Fill up quota |
522 | | - SpamSuccessfulUploads(timedLevelLimit.UploadQuota, client, 0); |
523 | | - |
524 | | - // Try to upload more levels after exceeding quota |
525 | | - for (int i = 0; i < uploadAttemptsAfterExceeding; i++) |
526 | | - { |
527 | | - GameLevelRequest level = PrepareLevelPublishRequest(client, i + timedLevelLimit.UploadQuota); |
528 | | - |
529 | | - HttpResponseMessage message = client.PostAsync("/lbp/startPublish", new StringContent(level.AsXML())).Result; |
530 | | - Assert.That(message.StatusCode, Is.EqualTo(Unauthorized)); |
531 | | - message = client.PostAsync("/lbp/publish", new StringContent(level.AsXML())).Result; |
532 | | - Assert.That(message.StatusCode, Is.EqualTo(Unauthorized)); |
533 | | - } |
534 | | - |
535 | | - // Check amount of levels |
536 | | - DatabaseList<GameLevel> levelsByUser = context.Database.GetLevelsByUser(user, 1000, 0, new(TokenGame.LittleBigPlanet3), user); |
537 | | - Assert.That(levelsByUser.TotalItems, Is.EqualTo(timedLevelLimit.UploadQuota)); |
538 | | - |
539 | | - // Ensure there were error notifications sent for each blocked request to both /startPublish and /publish |
540 | | - DatabaseList<GameNotification> newNotifications = context.Database.GetNotificationsByUser(user, 1000, 0); |
541 | | - Assert.That(newNotifications.TotalItems, Is.EqualTo(uploadAttemptsAfterExceeding * 2)); |
542 | | - } |
| 523 | + // Fill up half of quota |
| 524 | + publishAttempts += SpamPublishUniqueLevels(uploadConfig.UploadQuota / 2, client, publishAttempts); |
| 525 | + context.Database.Refresh(); |
543 | 526 |
|
544 | | - [Test] |
545 | | - [TestCase(2, 2)] |
546 | | - public void ResetTimedLevelLimitAfterExpiry(int levelQuota, int uploadAttemptsAfterExceeding) |
547 | | - { |
548 | | - using TestContext context = this.GetServer(); |
549 | | - GameUser user = context.CreateUser("thepublisher"); |
| 527 | + // There is rate-limit data in DB, but the rate-limit hasn't been triggered yet |
| 528 | + EntityUploadRateLimit? uploadLimit = context.Database.GetUploadRateLimit(user, GameDatabaseEntity.Level); |
| 529 | + Assert.That(uploadLimit, Is.Not.Null); |
| 530 | + Assert.That(uploadLimit!.UploadCount, Is.EqualTo(uploadConfig.UploadQuota / 2)); |
| 531 | + Assert.That(context.Database.GetRemainingTimeIfUploadRateLimitReached(user, GameDatabaseEntity.Level, uploadConfig.UploadQuota), Is.Null); |
| 532 | + |
| 533 | + // Try to upload more levels to reach quota |
| 534 | + publishAttempts += SpamPublishUniqueLevels(uploadConfig.UploadQuota / 2, client, publishAttempts); |
| 535 | + context.Database.Refresh(); |
550 | 536 |
|
551 | | - // Prepare config |
552 | | - GameServerConfig config = context.Server.Value.GameServerConfig; |
553 | | - EntityUploadRateLimitProperties timedLevelLimit = new() |
554 | | - { |
555 | | - Enabled = true, |
556 | | - UploadQuota = levelQuota, |
557 | | - // Having this be 0 causes the server to always set the expiry date to now, making the expiry date be not null but always expired, |
558 | | - // causing both /startPublish and /publish to always reset the limit after setting it in a previous /publish request, and allowing publish requests |
559 | | - TimeSpanHours = 0, |
560 | | - }; |
561 | | - config.NormalUserPermissions.LevelUploadRateLimit = timedLevelLimit; |
| 537 | + // Ensure there were no notifications sent |
| 538 | + Assert.That(context.Database.GetNotificationCountByUser(user), Is.Zero); |
562 | 539 |
|
563 | | - using HttpClient client = context.GetAuthenticatedClient(TokenType.Game, user); |
| 540 | + // There is rate-limit data in DB, and the rate-limit has been triggered |
| 541 | + uploadLimit = context.Database.GetUploadRateLimit(user, GameDatabaseEntity.Level); |
| 542 | + Assert.That(uploadLimit, Is.Not.Null); |
| 543 | + Assert.That(uploadLimit!.UploadCount, Is.EqualTo(uploadConfig.UploadQuota)); |
| 544 | + Assert.That(context.Database.GetRemainingTimeIfUploadRateLimitReached(user, GameDatabaseEntity.Level, uploadConfig.UploadQuota), Is.Not.Null); |
564 | 545 |
|
565 | | - // Upload level asset |
566 | | - HttpResponseMessage message = client.PostAsync($"/lbp/upload/{TEST_ASSET_HASH}", new ReadOnlyMemoryContent("LVLb"u8.ToArray())).Result; |
567 | | - Assert.That(message.StatusCode, Is.EqualTo(OK)); |
| 546 | + // levels are blocked |
| 547 | + publishAttempts += SpamPublishUniqueLevels(uploadAttemptsAfterExceeding, client, publishAttempts, Unauthorized); |
| 548 | + context.Database.Refresh(); |
568 | 549 |
|
569 | | - // Fill up quota |
570 | | - SpamSuccessfulUploads(timedLevelLimit.UploadQuota, client, 0); |
571 | | - |
572 | | - // Try to upload more levels after exceeding quota |
573 | | - SpamSuccessfulUploads(uploadAttemptsAfterExceeding, client, timedLevelLimit.UploadQuota); |
| 550 | + // Check amount of levels, and ensure there were notifications sent for every failed level |
| 551 | + Assert.That(context.Database.GetTotalLevelsByUser(user), Is.EqualTo(uploadConfig.UploadQuota)); |
| 552 | + Assert.That(context.Database.GetNotificationCountByUser(user), Is.EqualTo(uploadAttemptsAfterExceeding * 2)); |
574 | 553 |
|
575 | | - // Check amount of levels |
576 | | - DatabaseList<GameLevel> levelsByUser = context.Database.GetLevelsByUser(user, 1000, 0, new(TokenGame.LittleBigPlanet3), user); |
577 | | - Assert.That(levelsByUser.TotalItems, Is.EqualTo(timedLevelLimit.UploadQuota + uploadAttemptsAfterExceeding)); |
| 554 | + // Expire limit naturally by trying to publish again later |
| 555 | + context.Time.TimestampMilliseconds = 1000 * 60 * 60 * timeSpanHours + 10; |
| 556 | + publishAttempts += SpamPublishUniqueLevels(uploadAttemptsAfterExceeding, client, publishAttempts); |
| 557 | + context.Database.Refresh(); |
578 | 558 |
|
579 | | - // Ensure there were no notifications sent |
580 | | - DatabaseList<GameNotification> newNotifications = context.Database.GetNotificationsByUser(user, 1000, 0); |
581 | | - Assert.That(newNotifications.TotalItems, Is.EqualTo(0)); |
| 559 | + // there are more levels now, and no new notifications |
| 560 | + Assert.That(context.Database.GetTotalLevelsByUser(user), Is.EqualTo(uploadConfig.UploadQuota * 2)); |
| 561 | + Assert.That(context.Database.GetNotificationCountByUser(user), Is.EqualTo(uploadAttemptsAfterExceeding * 2)); |
582 | 562 | } |
583 | 563 |
|
584 | | - private void SpamSuccessfulUploads(int uploads, HttpClient client, int startIndex) |
| 564 | + private int SpamPublishUniqueLevels(int uploads, HttpClient client, int startIndex, HttpStatusCode expectedStatus = OK) |
585 | 565 | { |
586 | 566 | for (int i = 0; i < uploads; i++) |
587 | 567 | { |
588 | 568 | // Upload level |
589 | | - GameLevelRequest level = PrepareLevelPublishRequest(client, startIndex + i); |
| 569 | + GameLevelRequest level = PrepareUniqueLevelPublishRequest(client, startIndex + i); |
590 | 570 |
|
591 | 571 | HttpResponseMessage message = client.PostAsync("/lbp/startPublish", new StringContent(level.AsXML())).Result; |
592 | | - Assert.That(message.StatusCode, Is.EqualTo(OK)); |
| 572 | + Assert.That(message.StatusCode, Is.EqualTo(expectedStatus)); |
593 | 573 | message = client.PostAsync("/lbp/publish", new StringContent(level.AsXML())).Result; |
594 | | - Assert.That(message.StatusCode, Is.EqualTo(OK)); |
| 574 | + Assert.That(message.StatusCode, Is.EqualTo(expectedStatus)); |
595 | 575 | } |
| 576 | + |
| 577 | + return uploads; |
596 | 578 | } |
597 | 579 |
|
598 | | - private GameLevelRequest PrepareLevelPublishRequest(HttpClient client, int uniqueValue) |
| 580 | + private GameLevelRequest PrepareUniqueLevelPublishRequest(HttpClient client, int uniqueValue) |
599 | 581 | { |
600 | 582 | // upload root asset |
601 | 583 | ReadOnlySpan<byte> rootResource = new(Encoding.ASCII.GetBytes($"LVLb {uniqueValue}")); |
602 | | - |
603 | | - string rootHash = BitConverter.ToString(SHA1.HashData(rootResource)) |
604 | | - .Replace("-", "") |
605 | | - .ToLower(); |
| 584 | + string rootHash = BitConverter.ToString(SHA1.HashData(rootResource)).Replace("-", "").ToLower(); |
606 | 585 |
|
607 | 586 | HttpResponseMessage message = client.PostAsync($"/lbp/upload/{rootHash}", new ReadOnlyMemoryContent(rootResource.ToArray())).Result; |
608 | 587 | Assert.That(message.StatusCode, Is.EqualTo(OK)); |
|
0 commit comments