Skip to content

Commit a9e130a

Browse files
authored
feat: add support for AI (#965)
1 parent 2c8eedc commit a9e130a

21 files changed

+647
-15
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Threading.Tasks;
4+
using Box.V2.Models;
5+
using Box.V2.Test.Integration.Configuration;
6+
using Microsoft.VisualStudio.TestTools.UnitTesting;
7+
8+
namespace Box.V2.Test.Integration
9+
{
10+
[TestClass]
11+
public class BoxAIManagerIntegrationTests : TestInFolder
12+
{
13+
[TestMethod]
14+
public async Task SendAIQuestionAsync_ForSingleItem_ReturnsValidResponse()
15+
{
16+
var fileName = "[Single Item AI] Test File.txt";
17+
var fileContent = "Test file";
18+
var uploadedFile = await CreateSmallFromMemoryStream(FolderId, fileName, fileContent);
19+
20+
var request = new BoxAIAskRequest
21+
{
22+
Prompt = "What is the name of the file?",
23+
Items = new List<BoxAIAskItem>() { new BoxAIAskItem() { Id = uploadedFile.Id } },
24+
Mode = BoxAIAskMode.single_item_qa
25+
};
26+
27+
await Retry(async () =>
28+
{
29+
var response = await UserClient.BoxAIManager.SendAIQuestionAsync(request);
30+
31+
Assert.IsTrue(response.Answer.Contains(fileContent));
32+
Assert.IsTrue(response.CreatedAt < DateTimeOffset.Now);
33+
Assert.AreEqual(response.CompletionReason, "done");
34+
});
35+
}
36+
37+
[TestMethod]
38+
public async Task SendAIQuestionAsync_ForMultipleItems_ReturnsValidResponse()
39+
{
40+
var fileContent = "Test file";
41+
42+
var fileName1 = "[Multi Item AI] First Test File.txt";
43+
var uploadedFile1 = await CreateSmallFromMemoryStream(FolderId, fileName1, fileContent);
44+
45+
var fileName2 = "[Multi Item AI] Second test file.txt";
46+
var uploadedFile2 = await CreateSmallFromMemoryStream(FolderId, fileName2, fileContent);
47+
48+
var request = new BoxAIAskRequest
49+
{
50+
Prompt = "What is the content of these files?",
51+
Items = new List<BoxAIAskItem>()
52+
{
53+
new BoxAIAskItem() { Id = uploadedFile1.Id },
54+
new BoxAIAskItem() { Id = uploadedFile2.Id }
55+
},
56+
Mode = BoxAIAskMode.multiple_item_qa
57+
};
58+
59+
await Retry(async () =>
60+
{
61+
var response = await UserClient.BoxAIManager.SendAIQuestionAsync(request);
62+
63+
Assert.IsTrue(response.Answer.Contains(fileContent));
64+
Assert.IsTrue(response.CreatedAt < DateTimeOffset.Now);
65+
Assert.AreEqual(response.CompletionReason, "done");
66+
});
67+
}
68+
69+
[TestMethod]
70+
public async Task SendTextGenRequestAsync_ForValidPayload_ReturnsValidResponse()
71+
{
72+
var fileName = "[AI Text Gen] Test File.txt";
73+
var fileContent = "Test File";
74+
var uploadedFile = await CreateSmallFromMemoryStream(FolderId, fileName, fileContent);
75+
var date1 = DateTimeOffset.Parse("2013-05-16T15:27:57-07:00");
76+
var date2 = DateTimeOffset.Parse("2013-05-16T15:26:57-07:00");
77+
78+
var request = new BoxAITextGenRequest
79+
{
80+
Prompt = "What is the name of the file?",
81+
Items = new List<BoxAITextGenItem>() { new BoxAITextGenItem() { Id = uploadedFile.Id } },
82+
DialogueHistory = new List<BoxAIDialogueHistory>()
83+
{
84+
new BoxAIDialogueHistory() { Prompt = "What is the name of the file?", Answer = fileContent, CreatedAt = date1 },
85+
new BoxAIDialogueHistory() { Prompt = "What is the size of the file?", Answer = "10kb", CreatedAt = date2 }
86+
}
87+
};
88+
89+
await Retry(async () =>
90+
{
91+
var response = await UserClient.BoxAIManager.SendAITextGenRequestAsync(request);
92+
93+
Assert.IsTrue(response.Answer.Contains(fileContent));
94+
Assert.IsTrue(response.CreatedAt < DateTimeOffset.Now);
95+
Assert.AreEqual(response.CompletionReason, "done");
96+
});
97+
}
98+
}
99+
}

Box.V2.Test.Integration/Configuration/Commands/DisposableCommands/CreateFileCommand.cs

+36-12
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
using System.IO;
2+
using System.Text;
23
using System.Threading.Tasks;
34
using Box.V2.Models;
45

@@ -9,40 +10,63 @@ public class CreateFileCommand : CommandBase, IDisposableCommand
910
private readonly string _fileName;
1011
private readonly string _filePath;
1112
private readonly string _folderId;
13+
private readonly string _content;
14+
private readonly bool _isFileStream;
1215

1316
public string FileId;
1417
public BoxFile File;
1518

16-
public CreateFileCommand(string fileName, string filePath, string folderId = "0", CommandScope scope = CommandScope.Test, CommandAccessLevel accessLevel = CommandAccessLevel.User) : base(scope, accessLevel)
19+
public CreateFileCommand(string fileName, string filePath, string folderId = "0", CommandScope scope = CommandScope.Test,
20+
CommandAccessLevel accessLevel = CommandAccessLevel.User, string content = "") : base(scope, accessLevel)
1721
{
1822
_fileName = fileName;
1923
_filePath = filePath;
2024
_folderId = folderId;
25+
_content = content;
26+
if (!string.IsNullOrEmpty(_filePath) && !string.IsNullOrEmpty(_content))
27+
{
28+
throw new System.Exception("You can't have both filePath and content filled");
29+
}
30+
_isFileStream = !string.IsNullOrEmpty(_filePath);
2131
}
2232

2333
public async Task<string> Execute(IBoxClient client)
2434
{
25-
using (var fileStream = new FileStream(_filePath, FileMode.OpenOrCreate))
35+
if (_isFileStream)
2636
{
27-
var requestParams = new BoxFileRequest()
37+
using (var fileStream = new FileStream(_filePath, FileMode.OpenOrCreate))
2838
{
29-
Name = _fileName,
30-
Parent = new BoxRequestEntity() { Id = _folderId }
31-
};
32-
33-
var response = await client.FilesManager.UploadAsync(requestParams, fileStream);
34-
File = response;
35-
FileId = File.Id;
36-
return FileId;
39+
return await UploadFileAsync(client, fileStream);
40+
}
41+
}
42+
43+
var byteArray = Encoding.UTF8.GetBytes(_content);
44+
using (var memoryStream = new MemoryStream(byteArray))
45+
{
46+
return await UploadFileAsync(client, memoryStream);
3747
}
3848
}
3949

50+
private async Task<string> UploadFileAsync(IBoxClient client, Stream stream)
51+
{
52+
var requestParams = new BoxFileRequest()
53+
{
54+
Name = _fileName,
55+
Parent = new BoxRequestEntity() { Id = _folderId }
56+
};
57+
58+
var response = await client.FilesManager.UploadAsync(requestParams, stream);
59+
File = response;
60+
FileId = File.Id;
61+
return FileId;
62+
}
63+
4064
public async Task Dispose(IBoxClient client)
4165
{
4266
await client.FilesManager.DeleteAsync(FileId);
4367

4468
// for some reason file uploaded as admin cannot be purged from trash
45-
if(AccessLevel != CommandAccessLevel.Admin)
69+
if (AccessLevel != CommandAccessLevel.Admin)
4670
{
4771
await client.FilesManager.PurgeTrashedAsync(FileId);
4872
}

Box.V2.Test.Integration/Configuration/IntegrationTestBase.cs

+10-1
Original file line numberDiff line numberDiff line change
@@ -216,7 +216,8 @@ public static string ReadFromJson(string path)
216216
return File.ReadAllText(filePath);
217217
}
218218

219-
public static async Task<BoxFile> CreateSmallFile(string parentId = "0", CommandScope commandScope = CommandScope.Test, CommandAccessLevel accessLevel = CommandAccessLevel.User)
219+
public static async Task<BoxFile> CreateSmallFile(string parentId = "0", CommandScope commandScope = CommandScope.Test,
220+
CommandAccessLevel accessLevel = CommandAccessLevel.User)
220221
{
221222
var path = GetSmallFilePath();
222223
var ext = "";
@@ -233,6 +234,14 @@ public static async Task<BoxFile> CreateSmallFileAsAdmin(string parentId)
233234
return await CreateSmallFile(parentId, CommandScope.Test, CommandAccessLevel.Admin);
234235
}
235236

237+
public static async Task<BoxFile> CreateSmallFromMemoryStream(string parentId = "0", string filename = "", string content = "",
238+
CommandScope commandScope = CommandScope.Test, CommandAccessLevel accessLevel = CommandAccessLevel.User)
239+
{
240+
var createFileCommand = new CreateFileCommand(filename, "", parentId, commandScope, accessLevel, content);
241+
await ExecuteCommand(createFileCommand);
242+
return createFileCommand.File;
243+
}
244+
236245
public static async Task DeleteFile(string fileId)
237246
{
238247
await ExecuteCommand(new DeleteFileCommand(fileId));

Box.V2.Test/Box.V2.Test.csproj

+6
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,12 @@
2727
<Reference Include="System.Net.Http.WebRequest" />
2828
</ItemGroup>
2929
<ItemGroup>
30+
<None Update="Fixtures\BoxAI\SendAITextGenRequestSuccess200.json">
31+
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
32+
</None>
33+
<None Update="Fixtures\BoxAI\SendAIQuestion200.json">
34+
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
35+
</None>
3036
<None Update="Fixtures\BoxFileRequest\UpdateFileRequest200.json">
3137
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
3238
</None>

Box.V2.Test/BoxAIManagerTest.cs

+112
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Threading.Tasks;
4+
using Box.V2.Managers;
5+
using Box.V2.Models;
6+
using Microsoft.VisualStudio.TestTools.UnitTesting;
7+
using Moq;
8+
9+
namespace Box.V2.Test
10+
{
11+
[TestClass]
12+
public class BoxAIManagerTest : BoxResourceManagerTest
13+
{
14+
private readonly BoxAIManager _aiManager;
15+
16+
public BoxAIManagerTest()
17+
{
18+
_aiManager = new BoxAIManager(Config.Object, Service, Converter, AuthRepository);
19+
}
20+
21+
[TestMethod]
22+
public async Task SendAiQuestionAsync_Success()
23+
{
24+
/** Arrange **/
25+
IBoxRequest boxRequest = null;
26+
Handler.Setup(h => h.ExecuteAsync<BoxAIResponse>(It.IsAny<IBoxRequest>()))
27+
.Returns(Task.FromResult<IBoxResponse<BoxAIResponse>>(new BoxResponse<BoxAIResponse>()
28+
{
29+
Status = ResponseStatus.Success,
30+
ContentString = LoadFixtureFromJson("Fixtures/BoxAI/SendAiQuestion200.json")
31+
}))
32+
.Callback<IBoxRequest>(r => boxRequest = r);
33+
34+
var requestBody = new BoxAIAskRequest()
35+
{
36+
Mode = BoxAIAskMode.single_item_qa,
37+
Prompt = "What is the value provided by public APIs based on this document?",
38+
Items = new List<BoxAIAskItem>()
39+
{
40+
new BoxAIAskItem() { Id = "9842787262" }
41+
}
42+
};
43+
44+
/*** Act ***/
45+
BoxAIResponse response = await _aiManager.SendAIQuestionAsync(requestBody);
46+
47+
/*** Assert ***/
48+
// Request check
49+
Assert.IsNotNull(boxRequest);
50+
Assert.AreEqual(RequestMethod.Post, boxRequest.Method);
51+
Assert.AreEqual(new Uri("https://api.box.com/2.0/ai/ask"), boxRequest.AbsoluteUri);
52+
53+
// Response check
54+
Assert.AreEqual("Public APIs are important because of key and important reasons.", response.Answer);
55+
Assert.AreEqual("done", response.CompletionReason);
56+
Assert.AreEqual(DateTimeOffset.Parse("2012-12-12T10:53:43-08:00"), response.CreatedAt);
57+
}
58+
59+
60+
[TestMethod]
61+
public async Task SendAiGenerateTextRequestAsync_Success()
62+
{
63+
/** Arrange **/
64+
IBoxRequest boxRequest = null;
65+
Handler.Setup(h => h.ExecuteAsync<BoxAIResponse>(It.IsAny<IBoxRequest>()))
66+
.Returns(Task.FromResult<IBoxResponse<BoxAIResponse>>(new BoxResponse<BoxAIResponse>()
67+
{
68+
Status = ResponseStatus.Success,
69+
ContentString = LoadFixtureFromJson("Fixtures/BoxAI/SendAITextGenRequestSuccess200.json")
70+
}))
71+
.Callback<IBoxRequest>(r => boxRequest = r);
72+
73+
var requestBody = new BoxAITextGenRequest()
74+
{
75+
Prompt = "Write an email to a client about the importance of public APIs",
76+
Items = new List<BoxAITextGenItem>()
77+
{
78+
new BoxAITextGenItem() { Id = "12345678", Content = "More information about public APIs" }
79+
},
80+
DialogueHistory = new List<BoxAIDialogueHistory>()
81+
{
82+
new BoxAIDialogueHistory()
83+
{
84+
Prompt = "Make my email about public APIs sound more professional",
85+
Answer = "Here is the first draft of your professional email about public APIs",
86+
CreatedAt = DateTimeOffset.Parse("2013-12-12T10:53:43-08:00")
87+
},
88+
new BoxAIDialogueHistory()
89+
{
90+
Prompt = "Can you add some more information?",
91+
Answer = "Public API schemas provide necessary information to integrate with APIs...",
92+
CreatedAt = DateTimeOffset.Parse("2013-12-12T11:20:43-08:00")
93+
}
94+
}
95+
};
96+
97+
/*** Act ***/
98+
BoxAIResponse response = await _aiManager.SendAITextGenRequestAsync(requestBody);
99+
100+
/*** Assert ***/
101+
// Request check
102+
Assert.IsNotNull(boxRequest);
103+
Assert.AreEqual(RequestMethod.Post, boxRequest.Method);
104+
Assert.AreEqual(new Uri("https://api.box.com/2.0/ai/text_gen"), boxRequest.AbsoluteUri);
105+
106+
// Response check
107+
Assert.AreEqual("Public APIs are important because of key and important reasons.", response.Answer);
108+
Assert.AreEqual("done", response.CompletionReason);
109+
Assert.AreEqual(DateTimeOffset.Parse("2012-12-12T10:53:43-08:00"), response.CreatedAt);
110+
}
111+
}
112+
}

Box.V2.Test/BoxResourceManagerTest.cs

+2
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ public abstract class BoxResourceManagerTest
3131
protected Uri SignRequestUri = new Uri(Constants.SignRequestsEndpointString);
3232
protected Uri SignRequestWithPathUri = new Uri(Constants.SignRequestsWithPathEndpointString);
3333
protected Uri FileRequestsWithPathUri = new Uri(Constants.FileRequestsWithPathEndpointString);
34+
protected Uri AIWithPathUri = new Uri(Constants.AIWithPathEndpointString);
3435

3536
protected BoxResourceManagerTest()
3637
{
@@ -52,6 +53,7 @@ protected BoxResourceManagerTest()
5253
Config.SetupGet(x => x.SignTemplatesEndpointUri).Returns(new Uri(Constants.SignTemplatesEndpointString));
5354
Config.SetupGet(x => x.SignTemplatesEndpointWithPathUri).Returns(new Uri(Constants.SignTemplatesWithPathEndpointString));
5455
Config.SetupGet(x => x.FileRequestsEndpointWithPathUri).Returns(FileRequestsWithPathUri);
56+
Config.SetupGet(x => x.AIEndpointWithPathUri).Returns(AIWithPathUri);
5557
Config.SetupGet(x => x.RetryStrategy).Returns(new InstantRetryStrategy());
5658

5759
AuthRepository = new AuthRepository(Config.Object, Service, Converter, new OAuthSession("fakeAccessToken", "fakeRefreshToken", 3600, "bearer"));
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
{
2+
"answer": "Public APIs are important because of key and important reasons.",
3+
"completion_reason": "done",
4+
"created_at": "2012-12-12T10:53:43-08:00"
5+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
{
2+
"answer": "Public APIs are important because of key and important reasons.",
3+
"completion_reason": "done",
4+
"created_at": "2012-12-12T10:53:43-08:00"
5+
}

Box.V2/Box.V2.csproj

+5
Original file line numberDiff line numberDiff line change
@@ -123,6 +123,7 @@
123123
<Compile Include="Extensions\BoxExtensions.cs" />
124124
<Compile Include="Extensions\UriExtensions.cs" />
125125
<Compile Include="JWTAuth\JWTAuthRepository.cs" />
126+
<Compile Include="Managers\BoxAIManager.cs" />
126127
<Compile Include="Managers\BoxCollaborationWhitelistManager.cs" />
127128
<Compile Include="Managers\BoxCollectionsManager.cs" />
128129
<Compile Include="Managers\BoxDevicePinManager.cs" />
@@ -142,6 +143,7 @@
142143
<Compile Include="Managers\BoxLegalHoldPoliciesManager.cs" />
143144
<Compile Include="Managers\BoxRecentItemsManager.cs" />
144145
<Compile Include="Managers\BoxWebLinksManager.cs" />
146+
<Compile Include="Managers\IBoxAIManager.cs" />
145147
<Compile Include="Managers\IBoxCollaborationsManager.cs" />
146148
<Compile Include="Managers\IBoxCollaborationWhitelistManager.cs" />
147149
<Compile Include="Managers\IBoxCollectionsManager.cs" />
@@ -167,6 +169,8 @@
167169
<Compile Include="Managers\IBoxWebhooksManager.cs" />
168170
<Compile Include="Managers\IBoxWebLinksManager.cs" />
169171
<Compile Include="Managers\IBoxFileRequestsManager.cs" />
172+
<Compile Include="Models\BoxAIResponse.cs" />
173+
<Compile Include="Models\Request\BoxAIAskRequest.cs" />
170174
<Compile Include="Models\BoxApplication.cs" />
171175
<Compile Include="Models\BoxAssignmentCounts.cs" />
172176
<Compile Include="Models\BoxClassification.cs" />
@@ -249,6 +253,7 @@
249253
<Compile Include="Models\BoxGroupMembership.cs" />
250254
<Compile Include="Models\Request\BoxActionableByRequest.cs" />
251255
<Compile Include="Models\BoxSessionParts.cs" />
256+
<Compile Include="Models\Request\BoxAITextGenRequest.cs" />
252257
<Compile Include="Models\Request\BoxFileRequestUpdateRequest.cs" />
253258
<Compile Include="Models\Request\BoxFileRequestCopyRequest.cs" />
254259
<Compile Include="Models\Request\BoxFileUploadSessionRequest.cs" />

0 commit comments

Comments
 (0)