File: ApplicationModel\Docker\DockerfileBuilderIntegrationTests.cs
Web Access
Project: src\tests\Aspire.Hosting.Tests\Aspire.Hosting.Tests.csproj (Aspire.Hosting.Tests)
// 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 ASPIREDOCKERFILEBUILDER001 // Type is for evaluation purposes only and is subject to change or removal in future updates
 
using System.Text;
using Aspire.Hosting.ApplicationModel.Docker;
 
namespace Aspire.Hosting.Tests.ApplicationModel.Docker;
 
public class DockerfileBuilderIntegrationTests
{
    [Fact]
    public async Task ExampleFromProblemStatement_ProducesCorrectDockerfile()
    {
        // Arrange - this is the exact example from the problem statement
        var dockerfileBuilder = new DockerfileBuilder();
        var baseStage = dockerfileBuilder.From("node:20-bullseye", "builder");
        baseStage.WorkDir("/");
        baseStage.Run("apt-get update && apt-get install -y --no-install-recommends git ca-certificates && rm -rf /var/lib/apt/lists/*");
        baseStage.Copy("package*.json", "./");
        baseStage.Run("npm ci --silent");
        baseStage.Env("NODE_ENV", "production");
 
        var output = dockerfileBuilder.From("caddy:2.7.4-alpine");
        output.CopyFrom("builder", "/app/dist", "/srv");
        output.Copy("caddy.json", "/etc/caddy/caddy.json");
        output.Expose(80);
        output.Cmd(["cmd", "run", "--config", "/etc/caddy/caddy.json"]);
 
        using var stream = new MemoryStream();
        using var writer = new StreamWriter(stream, new UTF8Encoding(encoderShouldEmitUTF8Identifier: false));
        writer.NewLine = "\n"; // Use LF line endings for Dockerfiles
 
        // Act
        await dockerfileBuilder.WriteAsync(writer);
        await writer.FlushAsync();
 
        // Assert
        var content = Encoding.UTF8.GetString(stream.ToArray());
        var expectedContent = """
            FROM node:20-bullseye AS builder
            WORKDIR /
            RUN apt-get update && apt-get install -y --no-install-recommends git ca-certificates && rm -rf /var/lib/apt/lists/*
            COPY package*.json ./
            RUN npm ci --silent
            ENV NODE_ENV=production
 
            FROM caddy:2.7.4-alpine
            COPY --from=builder /app/dist /srv
            COPY caddy.json /etc/caddy/caddy.json
            EXPOSE 80
            CMD ["cmd","run","--config","/etc/caddy/caddy.json"]
 
            """.ReplaceLineEndings("\n");
        
        Assert.Equal(expectedContent, content);
    }
 
    [Fact]
    public void AddRemoveStatements_WorksCorrectly()
    {
        // Arrange
        var builder = new DockerfileBuilder();
        var stage = builder.From("nginx");
 
        // Act - Add multiple statements
        stage.WorkDir("/app");
        stage.Run("echo 'test'");
        stage.Env("TEST", "value");
 
        // Assert - can access and modify the statements collection
        Assert.Equal(4, stage.Statements.Count); // FROM + WORKDIR + RUN + ENV
 
        // Act - Remove the ENV statement
        stage.Statements.RemoveAt(3);
 
        // Assert
        Assert.Equal(3, stage.Statements.Count);
    }
 
    [Fact]
    public async Task MultipleStagesWithComplexCommands_ProducesCorrectOutput()
    {
        // Arrange
        var builder = new DockerfileBuilder();
        
        // Stage 1: Build stage
        var buildStage = builder.From("golang:1.20", "build");
        buildStage.WorkDir("/src");
        buildStage.Copy("go.mod", "./");
        buildStage.Copy("go.sum", "./");
        buildStage.Run("go mod download");
        buildStage.Copy(".", ".");
        buildStage.Run("CGO_ENABLED=0 GOOS=linux go build -o /app/server ./cmd/server");
 
        // Stage 2: Runtime stage
        var runtimeStage = builder.From("alpine:latest");
        runtimeStage.Run("apk --no-cache add ca-certificates");
        runtimeStage.WorkDir("/root/");
        runtimeStage.CopyFrom("build", "/app/server", "./");
        runtimeStage.Env("PORT", "8080");
        runtimeStage.Expose(8080);
        runtimeStage.Cmd(["./server"]);
 
        using var stream = new MemoryStream();
        using var writer = new StreamWriter(stream, new UTF8Encoding(encoderShouldEmitUTF8Identifier: false));
        writer.NewLine = "\n"; // Use LF line endings for Dockerfiles
 
        // Act
        await builder.WriteAsync(writer);
        await writer.FlushAsync();
 
        // Assert
        var content = Encoding.UTF8.GetString(stream.ToArray());
        var expectedContent = """
            FROM golang:1.20 AS build
            WORKDIR /src
            COPY go.mod ./
            COPY go.sum ./
            RUN go mod download
            COPY . .
            RUN CGO_ENABLED=0 GOOS=linux go build -o /app/server ./cmd/server
 
            FROM alpine:latest
            RUN apk --no-cache add ca-certificates
            WORKDIR /root/
            COPY --from=build /app/server ./
            ENV PORT=8080
            EXPOSE 8080
            CMD ["./server"]
 
            """.ReplaceLineEndings("\n");
        
        Assert.Equal(expectedContent, content);
    }
 
    [Fact]
    public void FluentAPI_SupportsMethodChaining()
    {
        // Arrange
        var builder = new DockerfileBuilder();
 
        // Act - demonstrate fluent API works correctly
        var stage = builder.From("node:18")
            .WorkDir("/app")
            .Copy("package.json", "./")
            .Run("npm install")
            .Copy(".", ".")
            .Env("NODE_ENV", "production")
            .Expose(3000)
            .Cmd(["npm", "start"]);
 
        // Assert
        Assert.Equal(8, stage.Statements.Count); // FROM + WORKDIR + COPY + RUN + COPY + ENV + EXPOSE + CMD
        Assert.Null(stage.StageName); // This stage has no name
    }
 
    [Fact]
    public void DockerfileBuilder_WithMultipleStages_ManagesStagesCorrectly()
    {
        // Arrange & Act
        var dockerfileBuilder = new DockerfileBuilder();
 
        // Assert
        Assert.Empty(dockerfileBuilder.Stages);
        
        // Act - add stages
        var stage1 = dockerfileBuilder.From("alpine");
        var stage2 = dockerfileBuilder.From("ubuntu");
        
        // Assert
        Assert.Equal(2, dockerfileBuilder.Stages.Count);
        Assert.Same(stage1, dockerfileBuilder.Stages[0]);
        Assert.Same(stage2, dockerfileBuilder.Stages[1]);
    }
}