-
-
Notifications
You must be signed in to change notification settings - Fork 72
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add rector rule for mocking fetches in tests
Currently only supports mocking single fetches.
- Loading branch information
Showing
5 changed files
with
262 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,109 @@ | ||
<?php | ||
|
||
declare(strict_types=1); | ||
|
||
namespace Graby\Maintenance\Rector; | ||
|
||
use PhpParser\Node; | ||
use PhpParser\Node\Stmt\Class_; | ||
use Rector\Core\NodeManipulator\ClassInsertManipulator; | ||
use Rector\Core\NodeManipulator\ClassManipulator; | ||
use Rector\Core\Rector\AbstractRector; | ||
use Rector\PHPUnit\NodeAnalyzer\TestsNodeAnalyzer; | ||
use Symplify\RuleDocGenerator\ValueObject\CodeSample\CodeSample; | ||
use Symplify\RuleDocGenerator\ValueObject\RuleDefinition; | ||
|
||
final class AddTestHelpersTraitRector extends AbstractRector | ||
{ | ||
/** | ||
* @var string | ||
*/ | ||
private const TEST_HELPERS_TRAIT = 'Tests\Graby\TestHelpers'; | ||
|
||
private ClassInsertManipulator $classInsertManipulator; | ||
private ClassManipulator $classManipulator; | ||
private TestsNodeAnalyzer $testsNodeAnalyzer; | ||
|
||
public function __construct( | ||
ClassInsertManipulator $classInsertManipulator, | ||
ClassManipulator $classManipulator, | ||
TestsNodeAnalyzer $testsNodeAnalyzer, | ||
) { | ||
$this->classInsertManipulator = $classInsertManipulator; | ||
$this->classManipulator = $classManipulator; | ||
$this->testsNodeAnalyzer = $testsNodeAnalyzer; | ||
} | ||
|
||
public function getRuleDefinition(): RuleDefinition | ||
{ | ||
return new RuleDefinition( | ||
'Add TestHelpers trait for classes using $this->getGrabyWithMock()', | ||
[ | ||
new CodeSample( | ||
<<<'CODE_SAMPLE' | ||
use PHPUnit\Framework\TestCase; | ||
final class ExampleTest extends TestCase | ||
{ | ||
public function testOne(): void | ||
{ | ||
$graby = $this->getGrabyWithMock('https://example.com'); | ||
} | ||
} | ||
CODE_SAMPLE | ||
, | ||
<<<'CODE_SAMPLE' | ||
use PHPUnit\Framework\TestCase; | ||
use Tests\Graby\TestHelpers; | ||
final class ExampleTest extends TestCase | ||
{ | ||
use TestHelpers; | ||
public function testOne(): void | ||
{ | ||
$graby = $this->getGrabyWithMock('https://example.com'); | ||
} | ||
} | ||
CODE_SAMPLE | ||
), | ||
] | ||
); | ||
} | ||
|
||
/** | ||
* @return array<class-string<Node>> | ||
*/ | ||
public function getNodeTypes(): array | ||
{ | ||
return [Class_::class]; | ||
} | ||
|
||
/** | ||
* @param Class_ $node | ||
*/ | ||
public function refactor(Node $node): ?Node | ||
{ | ||
if ($this->shouldSkipClass($node)) { | ||
return null; | ||
} | ||
|
||
$this->classInsertManipulator->addAsFirstTrait($node, self::TEST_HELPERS_TRAIT); | ||
|
||
return $node; | ||
} | ||
|
||
private function shouldSkipClass(Class_ $class): bool | ||
{ | ||
$usesMethod = (bool) $this->betterNodeFinder->findFirst( | ||
$class, | ||
fn (Node $node): bool => $this->testsNodeAnalyzer->isAssertMethodCallName($node, 'getGrabyWithMock') | ||
); | ||
|
||
if (!$usesMethod) { | ||
return true; | ||
} | ||
|
||
return $this->classManipulator->hasTrait($class, self::TEST_HELPERS_TRAIT); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,138 @@ | ||
<?php | ||
|
||
declare(strict_types=1); | ||
|
||
namespace Maintenance\Graby\Rector; | ||
|
||
use PhpParser\Node; | ||
use PhpParser\Node\Arg; | ||
use PhpParser\Node\Expr\Assign; | ||
use PhpParser\Node\Expr\MethodCall; | ||
use PhpParser\Node\Expr\New_; | ||
use PhpParser\Node\Expr\Variable; | ||
use PhpParser\Node\Identifier; | ||
use PhpParser\Node\Scalar\String_; | ||
use Rector\Core\Rector\AbstractRector; | ||
use Rector\NodeNestingScope\ParentScopeFinder; | ||
use Rector\NodeTypeResolver\Node\AttributeKey; | ||
use Symplify\RuleDocGenerator\ValueObject\CodeSample\CodeSample; | ||
use Symplify\RuleDocGenerator\ValueObject\RuleDefinition; | ||
|
||
final class MockGrabyResponseRector extends AbstractRector | ||
{ | ||
private ParentScopeFinder $parentScopeFinder; | ||
|
||
public function __construct(ParentScopeFinder $parentScopeFinder) | ||
{ | ||
$this->parentScopeFinder = $parentScopeFinder; | ||
} | ||
|
||
public function getRuleDefinition(): RuleDefinition | ||
{ | ||
return new RuleDefinition( | ||
'Replace Graby instance by one with a mocked requests and stores the response in a fixture.', | ||
[ | ||
new CodeSample( | ||
<<<'CODE_SAMPLE' | ||
$graby = new Graby($config); | ||
$res = $graby->fetchContent('http://example.com/foo'); | ||
CODE_SAMPLE | ||
, | ||
<<<'CODE_SAMPLE' | ||
$graby = $this->getGrabyWithMock( | ||
'/fixtures/content/http___example.com_foo.html', | ||
200, | ||
$config | ||
); | ||
$res = $graby->fetchContent('http://example.com/'); | ||
CODE_SAMPLE | ||
), | ||
] | ||
); | ||
} | ||
|
||
/** | ||
* @return array<class-string<Node>> | ||
*/ | ||
public function getNodeTypes(): array | ||
{ | ||
return [New_::class]; | ||
} | ||
|
||
/** | ||
* @param New_ $node | ||
*/ | ||
public function refactor(Node $node): ?Node | ||
{ | ||
$construction = $node; | ||
if (!$this->nodeNameResolver->isName($construction->class, 'Graby\Graby')) { | ||
return null; | ||
} | ||
|
||
/** @var Node $parentNode */ | ||
$parentNode = $construction->getAttribute(AttributeKey::PARENT_NODE); | ||
if (!($assignment = $parentNode) instanceof Assign) { | ||
return null; | ||
} | ||
|
||
if (!($grabyVariable = $assignment->var) instanceof Variable) { | ||
return null; | ||
} | ||
|
||
$scope = $this->parentScopeFinder->find($construction); | ||
if (null === $scope) { | ||
return null; | ||
} | ||
|
||
$fetchUrls = array_map( | ||
fn (Node $node): ?string => $this->getFetchUrl($grabyVariable, $node), | ||
$this->betterNodeFinder->find( | ||
(array) $scope->stmts, | ||
fn (Node $foundNode): bool => null !== $this->getFetchUrl($grabyVariable, $foundNode) | ||
) | ||
); | ||
|
||
if (1 !== \count($fetchUrls)) { | ||
return null; | ||
} | ||
|
||
$url = $fetchUrls[0]; | ||
$suffix = preg_match('(\.[a-z0-9]+$)', $url) ? '' : '.html'; | ||
$fileName = '/fixtures/content/' . preg_replace('#[^a-zA-Z0-9-_\.]#', '_', $url) . $suffix; | ||
|
||
$contents = file_get_contents($url); | ||
file_put_contents(__DIR__ . '/../../tests' . $fileName, $contents); | ||
|
||
return $this->nodeFactory->createLocalMethodCall('getGrabyWithMock', array_merge( | ||
[ | ||
new String_($fileName), | ||
200, | ||
], | ||
$construction->args | ||
)); | ||
} | ||
|
||
/** | ||
* Extracts URL from the AST node in the form <grabyVariable>->fetchContent('url'). | ||
*/ | ||
private function getFetchUrl(Variable $grabyVariable, Node $node): ?string | ||
{ | ||
if (!($methodCall = $node) instanceof MethodCall) { | ||
return null; | ||
} | ||
|
||
if (!$this->nodeNameResolver->areNamesEqual($methodCall->var, $grabyVariable)) { | ||
return null; | ||
} | ||
|
||
if (!($methodName = $methodCall->name) instanceof Identifier || !$this->nodeNameResolver->isName($methodName, 'fetchContent')) { | ||
return null; | ||
} | ||
|
||
if (1 !== \count($args = $methodCall->args) || !$args[0] instanceof Arg || !($url = $args[0]->value) instanceof String_) { | ||
return null; | ||
} | ||
|
||
return $url->value; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,6 +1,7 @@ | ||
parameters: | ||
level: 7 | ||
paths: | ||
- maintenance/Rector | ||
- src | ||
- tests | ||
|
||
|