-
Notifications
You must be signed in to change notification settings - Fork 885
Expand file tree
/
Copy pathAddJavaScriptAppTests.cs
More file actions
330 lines (261 loc) · 13.2 KB
/
AddJavaScriptAppTests.cs
File metadata and controls
330 lines (261 loc) · 13.2 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
#pragma warning disable ASPIREJAVASCRIPT001 // Type is for evaluation purposes only
using System.Diagnostics;
using Aspire.Hosting.ApplicationModel;
using Aspire.Hosting.Utils;
using Aspire.TestUtilities;
using Microsoft.Extensions.DependencyInjection;
namespace Aspire.Hosting.JavaScript.Tests;
public class AddJavaScriptAppTests
{
[Fact]
public async Task VerifyDockerfile()
{
using var tempDir = new TestTempDirectory();
using var builder = TestDistributedApplicationBuilder.Create(DistributedApplicationOperation.Publish, outputPath: tempDir.Path).WithResourceCleanUp(true);
var appDir = Path.Combine(tempDir.Path, "js");
Directory.CreateDirectory(appDir);
var yarnApp = builder.AddJavaScriptApp("js", appDir)
.WithYarn(installArgs: ["--immutable"])
.WithBuildScript("do", ["--build"]);
await ManifestUtils.GetManifest(yarnApp.Resource, tempDir.Path);
var dockerfilePath = Path.Combine(tempDir.Path, "js.Dockerfile");
await Verify(File.ReadAllText(dockerfilePath));
var dockerBuildAnnotation = yarnApp.Resource.Annotations.OfType<DockerfileBuildAnnotation>().Single();
Assert.False(dockerBuildAnnotation.HasEntrypoint);
}
[Fact]
public async Task VerifyDockerfileWhenPublishedAsStaticWebsiteWithoutSpaFallback()
{
using var tempDir = new TestTempDirectory();
using var builder = TestDistributedApplicationBuilder.Create(DistributedApplicationOperation.Publish, outputPath: tempDir.Path).WithResourceCleanUp(true);
var appDir = Path.Combine(tempDir.Path, "js");
Directory.CreateDirectory(appDir);
var yarnApp = builder.AddJavaScriptApp("js", appDir)
.WithYarn(installArgs: ["--immutable"])
.WithBuildScript("do", ["--build"])
.PublishAsStaticWebsite();
await ManifestUtils.GetManifest(yarnApp.Resource, tempDir.Path);
var dockerfilePath = Path.Combine(tempDir.Path, "js.Dockerfile");
await Verify(File.ReadAllText(dockerfilePath));
}
[Fact]
public async Task VerifyDockerfileWhenPublishedAsNodeServer()
{
using var tempDir = new TestTempDirectory();
using var builder = TestDistributedApplicationBuilder.Create(DistributedApplicationOperation.Publish, outputPath: tempDir.Path).WithResourceCleanUp(true);
var appDir = Path.Combine(tempDir.Path, "js");
Directory.CreateDirectory(appDir);
var yarnApp = builder.AddJavaScriptApp("js", appDir)
.WithYarn(installArgs: ["--immutable"])
.WithBuildScript("do", ["--build"])
.PublishAsNodeServer(".output/server/index.mjs", ".output");
await ManifestUtils.GetManifest(yarnApp.Resource, tempDir.Path);
var dockerfilePath = Path.Combine(tempDir.Path, "js.Dockerfile");
await Verify(File.ReadAllText(dockerfilePath));
}
[Fact]
public async Task VerifyDockerfileWhenPublishedAsNpmScript()
{
using var tempDir = new TestTempDirectory();
using var builder = TestDistributedApplicationBuilder.Create(DistributedApplicationOperation.Publish, outputPath: tempDir.Path).WithResourceCleanUp(true);
var appDir = Path.Combine(tempDir.Path, "js");
Directory.CreateDirectory(appDir);
var yarnApp = builder.AddJavaScriptApp("js", appDir)
.WithYarn(installArgs: ["--immutable"])
.WithBuildScript("do", ["--build"])
.PublishAsNpmScript("start", "-- --port $PORT");
await ManifestUtils.GetManifest(yarnApp.Resource, tempDir.Path);
var dockerfilePath = Path.Combine(tempDir.Path, "js.Dockerfile");
await Verify(File.ReadAllText(dockerfilePath));
}
[Theory]
[InlineData(true)]
[InlineData(false)]
public async Task VerifyPnpmDockerfile(bool hasLockFile)
{
using var tempDir = new TestTempDirectory();
using var builder = TestDistributedApplicationBuilder.Create(DistributedApplicationOperation.Publish, outputPath: tempDir.Path).WithResourceCleanUp(true);
// Create directory to ensure manifest generates correct relative build context path
var appDir = Path.Combine(tempDir.Path, "js");
Directory.CreateDirectory(appDir);
if (hasLockFile)
{
File.WriteAllText(Path.Combine(appDir, "pnpm-lock.yaml"), string.Empty);
}
var pnpmApp = builder.AddJavaScriptApp("js", appDir)
.WithPnpm(installArgs: ["--prefer-frozen-lockfile"])
.WithBuildScript("mybuild");
await ManifestUtils.GetManifest(pnpmApp.Resource, tempDir.Path);
var dockerfilePath = Path.Combine(tempDir.Path, "js.Dockerfile");
var dockerfileContents = File.ReadAllText(dockerfilePath);
await Verify(dockerfileContents);
}
[Fact]
public async Task PublishWithExistingDockerfileThrowsWhenRunScriptNameIsExplicit()
{
using var tempDir = new TestTempDirectory();
using var builder = TestDistributedApplicationBuilder.Create(DistributedApplicationOperation.Publish, outputPath: tempDir.Path).WithResourceCleanUp(true);
var appDir = CreateJavaScriptAppWithDockerfile(tempDir.Path);
var app = builder.AddJavaScriptApp("js", appDir, "migrate")
.WithBun();
var exception = await Assert.ThrowsAsync<InvalidOperationException>(() => ManifestUtils.GetManifest(app.Resource, tempDir.Path));
Assert.Contains("runScriptName", exception.Message);
Assert.Contains("WithRunScript", exception.Message);
Assert.Contains("Dockerfile", exception.Message);
}
[Fact]
public async Task PublishModelWithExistingDockerfileThrowsWhenRunScriptNameIsExplicit()
{
using var tempDir = new TestTempDirectory();
using var builder = TestDistributedApplicationBuilder.Create(DistributedApplicationOperation.Publish, outputPath: tempDir.Path).WithResourceCleanUp(true);
var appDir = CreateJavaScriptAppWithDockerfile(tempDir.Path);
builder.AddJavaScriptApp("js", appDir, "migrate")
.WithBun();
using var app = builder.Build();
var appModel = app.Services.GetRequiredService<DistributedApplicationModel>();
var exception = await Assert.ThrowsAsync<InvalidOperationException>(() => ManifestUtils.GetManifestForModel(appModel, tempDir.Path));
Assert.Contains("runScriptName", exception.Message);
Assert.Contains("WithRunScript", exception.Message);
Assert.Contains("Dockerfile", exception.Message);
}
[Fact]
public async Task PublishWithExistingDockerfileThrowsWhenWithRunScriptOverridesDefault()
{
using var tempDir = new TestTempDirectory();
using var builder = TestDistributedApplicationBuilder.Create(DistributedApplicationOperation.Publish, outputPath: tempDir.Path).WithResourceCleanUp(true);
var appDir = CreateJavaScriptAppWithDockerfile(tempDir.Path);
var app = builder.AddJavaScriptApp("js", appDir)
.WithBun()
.WithRunScript("migrate");
var exception = await Assert.ThrowsAsync<InvalidOperationException>(() => ManifestUtils.GetManifest(app.Resource, tempDir.Path));
Assert.Contains("runScriptName", exception.Message);
Assert.Contains("WithRunScript", exception.Message);
Assert.Contains("Dockerfile", exception.Message);
}
[Fact]
public async Task PublishWithExistingDockerfileAllowsImplicitDefaultRunScript()
{
using var tempDir = new TestTempDirectory();
using var builder = TestDistributedApplicationBuilder.Create(DistributedApplicationOperation.Publish, outputPath: tempDir.Path).WithResourceCleanUp(true);
var appDir = CreateJavaScriptAppWithDockerfile(tempDir.Path);
var app = builder.AddJavaScriptApp("js", appDir)
.WithBun();
var manifest = await ManifestUtils.GetManifest(app.Resource, tempDir.Path);
Assert.Equal("container.v1", manifest["type"]?.ToString());
}
[Fact]
public async Task PublishWithExistingDockerfileAllowsExplicitEntrypointOverride()
{
using var tempDir = new TestTempDirectory();
using var builder = TestDistributedApplicationBuilder.Create(DistributedApplicationOperation.Publish, outputPath: tempDir.Path).WithResourceCleanUp(true);
var appDir = CreateJavaScriptAppWithDockerfile(tempDir.Path);
var app = builder.AddJavaScriptApp("js", appDir, "migrate")
.WithBun()
.PublishAsDockerFile(container => container
.WithEntrypoint("bun")
.WithArgs("src/migrate.ts"));
var manifest = await ManifestUtils.GetManifest(app.Resource, tempDir.Path);
Assert.Equal("bun", manifest["entrypoint"]?.ToString());
Assert.Contains("src/migrate.ts", manifest.ToJsonString());
}
[Fact]
[RequiresFeature(TestFeature.Docker | TestFeature.DockerPluginBuildx)]
[OuterloopTest("Builds a Docker image to verify the generated pnpm Dockerfile works")]
public async Task VerifyPnpmDockerfileBuildSucceeds()
{
using var tempDir = new TestTempDirectory();
using var builder = TestDistributedApplicationBuilder.Create(DistributedApplicationOperation.Publish, outputPath: tempDir.Path).WithResourceCleanUp(true);
// Create app directory
var appDir = Path.Combine(tempDir.Path, "pnpm-app");
Directory.CreateDirectory(appDir);
// Create a minimal package.json with no dependencies
var packageJson = """
{
"name": "pnpm-test-app",
"version": "1.0.0",
"scripts": {
"build": "echo 'build completed'"
}
}
""";
await File.WriteAllTextAsync(Path.Combine(appDir, "package.json"), packageJson);
var pnpmApp = builder.AddJavaScriptApp("pnpm-app", appDir)
.WithPnpm()
.WithBuildScript("build");
await ManifestUtils.GetManifest(pnpmApp.Resource, tempDir.Path);
var dockerfilePath = Path.Combine(tempDir.Path, "pnpm-app.Dockerfile");
Assert.True(File.Exists(dockerfilePath), $"Dockerfile should exist at {dockerfilePath}");
// Read the generated Dockerfile and verify it contains the corepack enable pnpm command
var dockerfileContent = await File.ReadAllTextAsync(dockerfilePath);
Assert.Contains("corepack enable pnpm", dockerfileContent);
// Modify the Dockerfile to add NODE_TLS_REJECT_UNAUTHORIZED=0 for test environments
// that may have corporate proxies with self-signed certificates
var modifiedDockerfile = dockerfileContent.Replace(
"WORKDIR /app",
"WORKDIR /app\nENV NODE_TLS_REJECT_UNAUTHORIZED=0");
var dockerfileInContext = Path.Combine(appDir, "Dockerfile");
await File.WriteAllTextAsync(dockerfileInContext, modifiedDockerfile);
// Build the Docker image using docker build with host network for registry access
var imageName = $"aspire-pnpm-test-{Guid.NewGuid():N}";
var processStartInfo = new ProcessStartInfo
{
FileName = "docker",
Arguments = $"build --network=host -t {imageName} -f Dockerfile .",
WorkingDirectory = appDir,
RedirectStandardOutput = true,
RedirectStandardError = true,
UseShellExecute = false,
CreateNoWindow = true
};
using var process = Process.Start(processStartInfo);
Assert.NotNull(process);
var stdout = await process.StandardOutput.ReadToEndAsync();
var stderr = await process.StandardError.ReadToEndAsync();
await process.WaitForExitAsync();
// Clean up the image regardless of success/failure
try
{
using var cleanupProcess = Process.Start(new ProcessStartInfo
{
FileName = "docker",
Arguments = $"rmi {imageName}",
UseShellExecute = false,
CreateNoWindow = true,
RedirectStandardOutput = true,
RedirectStandardError = true
});
if (cleanupProcess != null)
{
await cleanupProcess.WaitForExitAsync();
}
}
catch
{
// Ignore cleanup errors
}
// Assert the build succeeded
Assert.True(process.ExitCode == 0, $"Docker build failed with exit code {process.ExitCode}.\nStdout: {stdout}\nStderr: {stderr}");
}
private static string CreateJavaScriptAppWithDockerfile(string rootDirectory)
{
var appDir = Path.Combine(rootDirectory, "js");
Directory.CreateDirectory(appDir);
var dockerfile = """
FROM oven/bun:1
WORKDIR /app
COPY . .
ENTRYPOINT ["bun","src/index.ts"]
""";
File.WriteAllText(Path.Combine(appDir, "Dockerfile"), dockerfile);
File.WriteAllText(Path.Combine(appDir, "package.json"), """
{
"scripts": {
"migrate": "bun src/migrate.ts"
}
}
""");
return appDir;
}
}