Issues (47)

tests/integration/IntegrationTestCase.php (1 issue)

Labels
Severity
1
<?php
2
3
namespace CaptainHook\App\Integration;
4
5
use Exception;
6
use PHPUnit\Framework\TestCase;
7
use Symfony\Component\Filesystem\Filesystem;
8
use Symfony\Component\Process\Process;
9
10
class IntegrationTestCase extends TestCase
11
{
12
    public const REPO_TEMPLATE = CH_PATH_FILES . '/template-integration';
13
14
    /**
15
     * @return Filesystem
16
     */
17
    protected function filesystem(): Filesystem
18
    {
19
        return new Filesystem();
20
    }
21
22
    /**
23
     * Sets up a repository to use for testing and returns its path.
24
     *
25
     * @param bool $runComposerUpdate Whether to run `composer update` in the repository.
26
     * @return string The path to the repository.
27
     */
28
    protected function setUpRepository(bool $runComposerUpdate = true): string
29
    {
30
        $repoPath = $this->initializeEmptyGitRepository();
31
        $this->filesystem()->mirror(self::REPO_TEMPLATE, $repoPath);
32
        $this->addLocalCaptainHookToComposer($repoPath, CH_PATH_ROOT);
33
34
        $this->mustRunInShell(['git', 'add', '.'], $repoPath);
35
        $this->mustRunInShell(['git', 'commit', '-m', 'My initial commit'], $repoPath);
36
37
        if ($runComposerUpdate === true) {
38
            $this->mustRunInShell(['composer', 'update', '--no-ansi', '--no-interaction'], $repoPath);
39
        }
40
41
        return $repoPath;
42
    }
43
44
    /**
45
     * Creates and initializes a Git repository in the system's
46
     * temporary directory.
47
     *
48
     * @return string The path to the Git repository.
49
     */
50
    protected function initializeEmptyGitRepository(): string
51
    {
52
        try {
53
            $repoPath = sys_get_temp_dir()
54
                . '/CaptainHook/tests/repo-'
55
                . time() . '-' . bin2hex(random_bytes(4));
56
        } catch (Exception $exception) {
57
            TestCase::fail($exception->getMessage());
58
        }
59
60
        $gitConfigFile = $repoPath . '/.git/config';
61
62
        $this->filesystem()->mkdir($repoPath);
63
64
        $this->mustRunInShell(['git', 'init', '--initial-branch=main', $repoPath]);
65
        $this->mustRunInShell(['git', 'config', '--file', $gitConfigFile, 'user.name', 'Acceptance Tester']);
66
        $this->mustRunInShell(['git', 'config', '--file', $gitConfigFile, 'user.email', '[email protected]']);
67
68
        return $repoPath;
69
    }
70
71
    /**
72
     * Runs a shell command
73
     *
74
     * @param array $command The command to run and its arguments listed as separate entries
75
     * @param string|null $cwd The working directory or null to use the working dir of the current PHP process
76
     * @param array|null $env The environment variables or null to use the same environment as the current PHP process
77
     * @param mixed $input The input as stream resource, scalar or \Traversable, or null for no input
78
     * @param bool $throwOnFailure Throw exception if the process fails
79
     * @return ProcessResult
80
     */
81
    protected function runInShell(
82
        array $command,
83
        ?string $cwd = null,
84
        ?array $env = null,
85
        $input = null,
86
        bool $throwOnFailure = false
87
    ): ProcessResult {
88
        $process = new Process($command, $cwd, $env, $input);
89
90
        if ($throwOnFailure === true) {
91
            $process->mustRun();
92
        } else {
93
            $process->run();
94
        }
95
96
        return new ProcessResult($process->getExitCode(), $process->getOutput(), $process->getErrorOutput());
0 ignored issues
show
It seems like $process->getExitCode() can also be of type null; however, parameter $exitCode of CaptainHook\App\Integrat...ssResult::__construct() does only seem to accept integer, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

96
        return new ProcessResult(/** @scrutinizer ignore-type */ $process->getExitCode(), $process->getOutput(), $process->getErrorOutput());
Loading history...
97
    }
98
99
    /**
100
     * Runs a shell command, throwing an exception if it fails.
101
     *
102
     * @param array $command The command to run and its arguments listed as separate entries
103
     * @param string|null $cwd The working directory or null to use the working dir of the current PHP process
104
     * @param array|null $env The environment variables or null to use the same environment as the current PHP process
105
     * @param mixed $input The input as stream resource, scalar or \Traversable, or null for no input
106
     * @return ProcessResult
107
     */
108
    protected function mustRunInShell(
109
        array $command,
110
        ?string $cwd = null,
111
        ?array $env = null,
112
        $input = null
113
    ): ProcessResult {
114
        return $this->runInShell($command, $cwd, $env, $input, true);
115
    }
116
117
    /**
118
     * Adds an entry to the "repositories" property in composer.json, pointing
119
     * to the local CaptainHook source code being tested.
120
     *
121
     * @param string $repositoryPath
122
     * @param string $localCaptainHookPath
123
     * @return void
124
     */
125
    protected function addLocalCaptainHookToComposer(
126
        string $repositoryPath,
127
        string $localCaptainHookPath
128
    ): void {
129
        $composerFile = $repositoryPath . '/composer.json';
130
        $composerContents = $this->getJson($composerFile);
131
132
        if (!isset($composerContents['repositories'])) {
133
            $composerContents['repositories'] = [];
134
        }
135
136
        $composerContents['repositories'][] = [
137
            'type' => 'path',
138
            'url' => $localCaptainHookPath,
139
            'options' => [
140
                'symlink' => true,
141
            ],
142
        ];
143
144
        $this->writeJson($composerFile, $composerContents);
145
    }
146
147
    /**
148
     * Enables a hook in captainhook.json and configures actions for it.
149
     *
150
     * @param string $repositoryPath
151
     * @param string $hookName
152
     * @param array $actions
153
     * @return void
154
     */
155
    protected function enableHook(
156
        string $repositoryPath,
157
        string $hookName,
158
        array $actions = []
159
    ): void {
160
        $captainHookFile = $repositoryPath . '/captainhook.json';
161
        $captainHookContents = $this->getJson($captainHookFile);
162
163
        $captainHookContents[$hookName] = [
164
            'enabled' => true,
165
            'actions' => $actions,
166
        ];
167
168
        $this->writeJson($captainHookFile, $captainHookContents);
169
    }
170
171
    /**
172
     * Sets a config value in captainhook.json
173
     *
174
     * @param string $repositoryPath
175
     * @param string $configName
176
     * @param mixed $value
177
     * @return void
178
     */
179
    protected function setConfig(string $repositoryPath, string $configName, $value): void
180
    {
181
        $captainHookFile = $repositoryPath . '/captainhook.json';
182
        $captainHookContents = $this->getJson($captainHookFile);
183
184
        $captainHookContents['config'][$configName] = $value;
185
186
        $this->writeJson($captainHookFile, $captainHookContents);
187
    }
188
189
    /**
190
     * Returns the parsed contents of a JSON file.
191
     *
192
     * @param string $filename
193
     * @return array
194
     */
195
    private function getJson(string $filename): array
196
    {
197
        TestCase::assertFileExists($filename);
198
199
        $json = json_decode(file_get_contents($filename), true);
200
201
        if ($json === null) {
202
            TestCase::fail(json_last_error_msg());
203
        }
204
205
        return $json;
206
    }
207
208
    /**
209
     * Encodes $contents as JSON and writes it to $filename.
210
     *
211
     * @param string $filename
212
     * @param array $contents
213
     * @return void
214
     */
215
    private function writeJson(string $filename, array $contents): void
216
    {
217
        $this->filesystem()->dumpFile(
218
            $filename,
219
            json_encode(
220
                $contents,
221
                JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE
222
            )
223
        );
224
    }
225
}
226