diff --git a/app/Models/Project.php b/app/Models/Project.php index 59ffa546df..b64327bff1 100644 --- a/app/Models/Project.php +++ b/app/Models/Project.php @@ -7,6 +7,7 @@ use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Relations\BelongsToMany; use Illuminate\Database\Eloquent\Relations\HasMany; +use Illuminate\Database\Eloquent\Relations\HasManyThrough; use Illuminate\Database\Eloquent\Relations\HasOne; use Illuminate\Support\Carbon; use Illuminate\Support\Facades\Auth; @@ -291,6 +292,14 @@ public function builds(): HasMany return $this->hasMany(Build::class, 'projectid'); } + /** + * @return HasManyThrough + */ + public function tests(): HasManyThrough + { + return $this->hasManyThrough(Test::class, Build::class, 'projectid', 'buildid'); + } + /** * @return HasOne */ diff --git a/graphql/schema.graphql b/graphql/schema.graphql index 9db3ef7e37..432dc0b806 100644 --- a/graphql/schema.graphql +++ b/graphql/schema.graphql @@ -363,6 +363,11 @@ type Project { onlyParents: Boolean = true @scope ): [Build!]! @hasMany(type: CONNECTION) + "A shortcut to all tests for this project for convenience. Not guaranteed to be efficient." + tests( + filters: _ @filter + ): [Test!]! @hasManyThrough(type: CONNECTION) + """ An efficient way to get the number of builds matching a given filter. """ diff --git a/tests/Feature/GraphQL/ProjectTypeTest.php b/tests/Feature/GraphQL/ProjectTypeTest.php index 46f68c4af0..af35234926 100644 --- a/tests/Feature/GraphQL/ProjectTypeTest.php +++ b/tests/Feature/GraphQL/ProjectTypeTest.php @@ -3,6 +3,7 @@ namespace Tests\Feature\GraphQL; use App\Models\Project; +use App\Models\TestOutput; use App\Models\User; use Illuminate\Foundation\Testing\DatabaseTransactions; use Illuminate\Support\Str; @@ -1274,4 +1275,189 @@ public function testBuildCountFieldWithFilters(): void ], ]); } + + public function testTestsRelationship(): void + { + $output = TestOutput::create([ + 'path' => 'a', + 'command' => 'b', + 'output' => 'c', + ]); + + // First build with two tests. + $this->projects['public1']->builds()->create([ + 'name' => 'build1', + 'uuid' => Str::uuid(), + ])->tests()->createMany([ + [ + 'testname' => 'test1', + 'status' => 'passed', + 'outputid' => $output->id, + ], + [ + 'testname' => 'test2', + 'status' => 'failed', + 'outputid' => $output->id, + ], + ]); + + // Second build with a single test. + $this->projects['public1']->builds()->create([ + 'name' => 'build2', + 'uuid' => Str::uuid(), + ])->tests()->create([ + 'testname' => 'test3', + 'status' => 'notrun', + 'outputid' => $output->id, + ]); + + // A test belonging to a different project should not be returned. + $this->projects['public2']->builds()->create([ + 'name' => 'build3', + 'uuid' => Str::uuid(), + ])->tests()->create([ + 'testname' => 'test4', + 'status' => 'passed', + 'outputid' => $output->id, + ]); + + $this->graphQL(' + query($projectId: ID!) { + project(id: $projectId) { + tests { + edges { + node { + name + status + } + } + } + } + } + ', [ + 'projectId' => $this->projects['public1']->id, + ])->assertExactJson([ + 'data' => [ + 'project' => [ + 'tests' => [ + 'edges' => [ + [ + 'node' => [ + 'name' => 'test1', + 'status' => 'PASSED', + ], + ], + [ + 'node' => [ + 'name' => 'test2', + 'status' => 'FAILED', + ], + ], + [ + 'node' => [ + 'name' => 'test3', + 'status' => 'NOT_RUN', + ], + ], + ], + ], + ], + ], + ]); + + $output->delete(); + } + + /** + * Tests that a project with no builds (and therefore no tests) returns an + * empty tests relationship. + */ + public function testTestsRelationshipEmpty(): void + { + $this->graphQL(' + query($projectId: ID!) { + project(id: $projectId) { + tests { + edges { + node { + name + } + } + } + } + } + ', [ + 'projectId' => $this->projects['public1']->id, + ])->assertExactJson([ + 'data' => [ + 'project' => [ + 'tests' => [ + 'edges' => [], + ], + ], + ], + ]); + } + + /** + * Tests that the project's tests relationship can be filtered. + */ + public function testTestsRelationshipWithFilters(): void + { + $output = TestOutput::create([ + 'path' => 'a', + 'command' => 'b', + 'output' => 'c', + ]); + + $this->projects['public1']->builds()->create([ + 'name' => 'build1', + 'uuid' => Str::uuid(), + ])->tests()->createMany([ + [ + 'testname' => 'passing_test', + 'status' => 'passed', + 'outputid' => $output->id, + ], + [ + 'testname' => 'failing_test', + 'status' => 'failed', + 'outputid' => $output->id, + ], + ]); + + $this->graphQL(' + query($projectId: ID!) { + project(id: $projectId) { + tests(filters: { eq: { status: FAILED } }) { + edges { + node { + name + status + } + } + } + } + } + ', [ + 'projectId' => $this->projects['public1']->id, + ])->assertExactJson([ + 'data' => [ + 'project' => [ + 'tests' => [ + 'edges' => [ + [ + 'node' => [ + 'name' => 'failing_test', + 'status' => 'FAILED', + ], + ], + ], + ], + ], + ], + ]); + + $output->delete(); + } }