|
1 | 1 | package main |
2 | 2 |
|
3 | 3 | import ( |
| 4 | + "context" |
| 5 | + "os" |
| 6 | + "path/filepath" |
4 | 7 | "strings" |
5 | 8 | "testing" |
6 | 9 |
|
7 | 10 | "github.com/bufbuild/buf/private/bufpkg/bufremoteplugin/bufremotepluginconfig" |
8 | 11 | "github.com/bufbuild/buf/private/pkg/encoding" |
9 | 12 | "github.com/stretchr/testify/assert" |
10 | 13 | "github.com/stretchr/testify/require" |
| 14 | + |
| 15 | + "github.com/bufbuild/plugins/internal/source" |
11 | 16 | ) |
12 | 17 |
|
13 | 18 | func TestUpdatePluginDeps(t *testing.T) { |
@@ -138,3 +143,156 @@ plugin_version: v1.0.0 |
138 | 143 | }) |
139 | 144 | } |
140 | 145 | } |
| 146 | + |
| 147 | +// TestRunDependencyOrdering tests the end-to-end behavior of run() with dependency ordering. |
| 148 | +// It verifies that when creating multiple plugin versions in one run, they are processed |
| 149 | +// in dependency order and consumers reference the newly created dependency versions. |
| 150 | +func TestRunDependencyOrdering(t *testing.T) { |
| 151 | + t.Parallel() |
| 152 | + ctx := context.Background() |
| 153 | + tmpDir := t.TempDir() |
| 154 | + |
| 155 | + // Setup: Create complete repository structure |
| 156 | + setupTestRepository(t, tmpDir) |
| 157 | + |
| 158 | + // Mock fetcher that returns new versions for our test plugins |
| 159 | + // Cache keys are formatted as "github-owner-repository" |
| 160 | + fetcher := &mockFetcher{ |
| 161 | + versions: map[string]string{ |
| 162 | + "github-test-base-plugin": "v2.0.0", |
| 163 | + "github-test-consumer-plugin": "v2.0.0", |
| 164 | + }, |
| 165 | + } |
| 166 | + |
| 167 | + // Run the fetcher |
| 168 | + created, err := run(ctx, tmpDir, fetcher) |
| 169 | + require.NoError(t, err) |
| 170 | + |
| 171 | + // Verify plugins were created in dependency order |
| 172 | + require.Len(t, created, 2, "should create 2 new plugin versions") |
| 173 | + assert.Equal(t, "base-plugin", created[0].name, "base-plugin should be created first (no dependencies)") |
| 174 | + assert.Equal(t, "v2.0.0", created[0].newVersion) |
| 175 | + assert.Equal(t, "consumer-plugin", created[1].name, "consumer-plugin should be created second (depends on base-plugin)") |
| 176 | + assert.Equal(t, "v2.0.0", created[1].newVersion) |
| 177 | + |
| 178 | + // Verify consumer references the newly created base-plugin v2.0.0 |
| 179 | + consumerYAMLPath := filepath.Join(tmpDir, "plugins", "test", "consumer-plugin", "v2.0.0", "buf.plugin.yaml") |
| 180 | + content, err := os.ReadFile(consumerYAMLPath) |
| 181 | + require.NoError(t, err, "should be able to read created consumer plugin config") |
| 182 | + |
| 183 | + var config bufremotepluginconfig.ExternalConfig |
| 184 | + err = encoding.UnmarshalJSONOrYAMLStrict(content, &config) |
| 185 | + require.NoError(t, err, "should be able to parse consumer plugin config") |
| 186 | + |
| 187 | + require.Len(t, config.Deps, 1, "consumer should have one dependency") |
| 188 | + assert.Equal(t, "buf.build/test/base-plugin:v2.0.0", config.Deps[0].Plugin, |
| 189 | + "consumer should reference newly created base-plugin v2.0.0, not the old v1.0.0") |
| 190 | +} |
| 191 | + |
| 192 | +// mockFetcher returns predetermined versions for testing. |
| 193 | +type mockFetcher struct { |
| 194 | + versions map[string]string // maps cache key (e.g., "github-owner-repo") -> version to return |
| 195 | +} |
| 196 | + |
| 197 | +func (m *mockFetcher) Fetch(_ context.Context, config *source.Config) (string, error) { |
| 198 | + key := config.CacheKey() |
| 199 | + if version, ok := m.versions[key]; ok { |
| 200 | + return version, nil |
| 201 | + } |
| 202 | + // Return a default version if not in map |
| 203 | + return "v1.0.0", nil |
| 204 | +} |
| 205 | + |
| 206 | +// setupTestRepository creates a complete test repository structure with: |
| 207 | +// - plugins/ directory with base-plugin and consumer-plugin |
| 208 | +// - source.yaml files for version detection |
| 209 | +// - .github/docker/ directory with base images. |
| 210 | +func setupTestRepository(t *testing.T, tmpDir string) { |
| 211 | + t.Helper() |
| 212 | + |
| 213 | + // Create base Docker images directory (required by run()) |
| 214 | + baseImageDir := filepath.Join(tmpDir, ".github", "docker") |
| 215 | + require.NoError(t, os.MkdirAll(baseImageDir, 0755)) |
| 216 | + |
| 217 | + // Create required docker/dockerfile base image |
| 218 | + dockerfileImage := `FROM docker/dockerfile:1.19 |
| 219 | +` |
| 220 | + require.NoError(t, os.WriteFile(filepath.Join(baseImageDir, "Dockerfile.dockerfile"), []byte(dockerfileImage), 0644)) |
| 221 | + |
| 222 | + // Create golang base image |
| 223 | + golangImage := `FROM golang:1.22.0-bookworm |
| 224 | +` |
| 225 | + require.NoError(t, os.WriteFile(filepath.Join(baseImageDir, "Dockerfile.golang"), []byte(golangImage), 0644)) |
| 226 | + |
| 227 | + // Setup base-plugin v1.0.0 |
| 228 | + basePluginDir := filepath.Join(tmpDir, "plugins", "test", "base-plugin") |
| 229 | + require.NoError(t, os.MkdirAll(filepath.Join(basePluginDir, "v1.0.0"), 0755)) |
| 230 | + |
| 231 | + // Create source.yaml for base-plugin |
| 232 | + baseSourceYAML := `source: |
| 233 | + github: |
| 234 | + owner: test |
| 235 | + repository: base-plugin |
| 236 | +` |
| 237 | + require.NoError(t, os.WriteFile(filepath.Join(basePluginDir, "source.yaml"), []byte(baseSourceYAML), 0644)) |
| 238 | + |
| 239 | + // Create buf.plugin.yaml for base-plugin v1.0.0 |
| 240 | + basePluginYAML := `version: v1 |
| 241 | +name: buf.build/test/base-plugin |
| 242 | +plugin_version: v1.0.0 |
| 243 | +output_languages: |
| 244 | + - go |
| 245 | +` |
| 246 | + require.NoError(t, os.WriteFile( |
| 247 | + filepath.Join(basePluginDir, "v1.0.0", "buf.plugin.yaml"), |
| 248 | + []byte(basePluginYAML), |
| 249 | + 0644, |
| 250 | + )) |
| 251 | + |
| 252 | + // Create Dockerfile for base-plugin v1.0.0 |
| 253 | + baseDockerfile := `FROM golang:1.22.0-bookworm |
| 254 | +COPY --from=base /binary /usr/local/bin/protoc-gen-base |
| 255 | +` |
| 256 | + require.NoError(t, os.WriteFile( |
| 257 | + filepath.Join(basePluginDir, "v1.0.0", "Dockerfile"), |
| 258 | + []byte(baseDockerfile), |
| 259 | + 0644, |
| 260 | + )) |
| 261 | + |
| 262 | + // Setup consumer-plugin v1.0.0 |
| 263 | + consumerPluginDir := filepath.Join(tmpDir, "plugins", "test", "consumer-plugin") |
| 264 | + require.NoError(t, os.MkdirAll(filepath.Join(consumerPluginDir, "v1.0.0"), 0755)) |
| 265 | + |
| 266 | + // Create source.yaml for consumer-plugin |
| 267 | + consumerSourceYAML := `source: |
| 268 | + github: |
| 269 | + owner: test |
| 270 | + repository: consumer-plugin |
| 271 | +` |
| 272 | + require.NoError(t, os.WriteFile(filepath.Join(consumerPluginDir, "source.yaml"), []byte(consumerSourceYAML), 0644)) |
| 273 | + |
| 274 | + // Create buf.plugin.yaml for consumer-plugin v1.0.0 that depends on base-plugin v1.0.0 |
| 275 | + consumerPluginYAML := `version: v1 |
| 276 | +name: buf.build/test/consumer-plugin |
| 277 | +plugin_version: v1.0.0 |
| 278 | +deps: |
| 279 | + - plugin: buf.build/test/base-plugin:v1.0.0 |
| 280 | +output_languages: |
| 281 | + - go |
| 282 | +` |
| 283 | + require.NoError(t, os.WriteFile( |
| 284 | + filepath.Join(consumerPluginDir, "v1.0.0", "buf.plugin.yaml"), |
| 285 | + []byte(consumerPluginYAML), |
| 286 | + 0644, |
| 287 | + )) |
| 288 | + |
| 289 | + // Create Dockerfile for consumer-plugin v1.0.0 |
| 290 | + consumerDockerfile := `FROM golang:1.22.0-bookworm |
| 291 | +COPY --from=consumer /binary /usr/local/bin/protoc-gen-consumer |
| 292 | +` |
| 293 | + require.NoError(t, os.WriteFile( |
| 294 | + filepath.Join(consumerPluginDir, "v1.0.0", "Dockerfile"), |
| 295 | + []byte(consumerDockerfile), |
| 296 | + 0644, |
| 297 | + )) |
| 298 | +} |
0 commit comments