|
1 | | -// ----------------------------------------------------------------------- |
| 1 | +// ----------------------------------------------------------------------- |
2 | 2 | // <copyright file="SkillServerIntegrationTests.cs" company="Petabridge, LLC"> |
3 | 3 | // Copyright (C) 2026 - 2026 Petabridge, LLC <https://petabridge.com> |
4 | 4 | // </copyright> |
@@ -481,4 +481,94 @@ public async Task ApiKeyManagement_WithoutAuth_Returns401() |
481 | 481 | var response = await _fixture.HttpClient.GetAsync("/api-keys", ct); |
482 | 482 | Assert.Equal(HttpStatusCode.Unauthorized, response.StatusCode); |
483 | 483 | } |
| 484 | + |
| 485 | + [Fact] |
| 486 | + public async Task UploadSkill_DuplicateVersion_Returns409() |
| 487 | + { |
| 488 | + var ct = TestContext.Current.CancellationToken; |
| 489 | + var skillName = $"dup-test-{Guid.NewGuid():N}"[..20]; |
| 490 | + |
| 491 | + var skillContent = $""" |
| 492 | + --- |
| 493 | + name: {skillName} |
| 494 | + description: Testing duplicate version |
| 495 | + --- |
| 496 | +
|
| 497 | + # Duplicate Test |
| 498 | + """; |
| 499 | + |
| 500 | + // Upload the first version |
| 501 | + using var content1 = new MultipartFormDataContent(); |
| 502 | + content1.Add(new StringContent(skillName), "name"); |
| 503 | + content1.Add(new StringContent("1.0.0"), "version"); |
| 504 | + |
| 505 | + var fileContent1 = new ByteArrayContent(Encoding.UTF8.GetBytes(skillContent)); |
| 506 | + fileContent1.Headers.ContentType = new MediaTypeHeaderValue("text/markdown"); |
| 507 | + content1.Add(fileContent1, "file", "SKILL.md"); |
| 508 | + |
| 509 | + var uploadResponse1 = await _fixture.AuthenticatedHttpClient.PostAsync("/skills", content1, ct); |
| 510 | + Assert.Equal(HttpStatusCode.Created, uploadResponse1.StatusCode); |
| 511 | + |
| 512 | + // Upload the same version again - should return 409 Conflict |
| 513 | + using var content2 = new MultipartFormDataContent(); |
| 514 | + content2.Add(new StringContent(skillName), "name"); |
| 515 | + content2.Add(new StringContent("1.0.0"), "version"); |
| 516 | + |
| 517 | + var fileContent2 = new ByteArrayContent(Encoding.UTF8.GetBytes(skillContent)); |
| 518 | + fileContent2.Headers.ContentType = new MediaTypeHeaderValue("text/markdown"); |
| 519 | + content2.Add(fileContent2, "file", "SKILL.md"); |
| 520 | + |
| 521 | + var uploadResponse2 = await _fixture.AuthenticatedHttpClient.PostAsync("/skills", content2, ct); |
| 522 | + Assert.Equal(HttpStatusCode.Conflict, uploadResponse2.StatusCode); |
| 523 | + |
| 524 | + // Verify the response body contains the duplicate_version error |
| 525 | + var errorBody = await uploadResponse2.Content.ReadFromJsonAsync<SkillServer.Models.ErrorResponse>(ct); |
| 526 | + Assert.NotNull(errorBody); |
| 527 | + Assert.Equal("duplicate_version", errorBody.Error); |
| 528 | + Assert.Contains(errorBody.Message!, "already exists"); |
| 529 | + } |
| 530 | + |
| 531 | + [Fact] |
| 532 | + public async Task UploadSkill_DifferentVersions_AreAllowed() |
| 533 | + { |
| 534 | + var ct = TestContext.Current.CancellationToken; |
| 535 | + var skillName = $"ver-test-{Guid.NewGuid():N}"[..20]; |
| 536 | + |
| 537 | + var skillContent = $""" |
| 538 | + --- |
| 539 | + name: {skillName} |
| 540 | + description: Testing different versions |
| 541 | + --- |
| 542 | +
|
| 543 | + # Version Test |
| 544 | + """; |
| 545 | + |
| 546 | + // Upload v1.0.0 |
| 547 | + using var content1 = new MultipartFormDataContent(); |
| 548 | + content1.Add(new StringContent(skillName), "name"); |
| 549 | + content1.Add(new StringContent("1.0.0"), "version"); |
| 550 | + |
| 551 | + var fileContent1 = new ByteArrayContent(Encoding.UTF8.GetBytes(skillContent)); |
| 552 | + fileContent1.Headers.ContentType = new MediaTypeHeaderValue("text/markdown"); |
| 553 | + content1.Add(fileContent1, "file", "SKILL.md"); |
| 554 | + |
| 555 | + var uploadResponse1 = await _fixture.AuthenticatedHttpClient.PostAsync("/skills", content1, ct); |
| 556 | + Assert.Equal(HttpStatusCode.Created, uploadResponse1.StatusCode); |
| 557 | + |
| 558 | + // Upload v2.0.0 - should succeed |
| 559 | + using var content2 = new MultipartFormDataContent(); |
| 560 | + content2.Add(new StringContent(skillName), "name"); |
| 561 | + content2.Add(new StringContent("2.0.0"), "version"); |
| 562 | + |
| 563 | + var fileContent2 = new ByteArrayContent(Encoding.UTF8.GetBytes(skillContent)); |
| 564 | + fileContent2.Headers.ContentType = new MediaTypeHeaderValue("text/markdown"); |
| 565 | + content2.Add(fileContent2, "file", "SKILL.md"); |
| 566 | + |
| 567 | + var uploadResponse2 = await _fixture.AuthenticatedHttpClient.PostAsync("/skills", content2, ct); |
| 568 | + Assert.Equal(HttpStatusCode.Created, uploadResponse2.StatusCode); |
| 569 | + |
| 570 | + // Verify both versions exist |
| 571 | + var versions = await _fixture.Client.GetSkillVersionsAsync(skillName, ct); |
| 572 | + Assert.Equal(2, versions.Count); |
| 573 | + } |
484 | 574 | } |
0 commit comments