From 64cf06e9e7e77aabf92c6c9af9a551fbcc3345a7 Mon Sep 17 00:00:00 2001 From: Marko Date: Mon, 6 Oct 2025 11:25:41 +0200 Subject: [PATCH 1/9] feat(CDA-1565): Add command for changing compatibility level --- README.md | 2 +- docker/docker-compose.ci.yml | 1 - docker/docker-compose.yml | 1 - .../SetCompatibilityModeForSchemaCommand.php | 43 +++++++++++ ...tCompatibilityModeForSchemaCommandTest.php | 74 +++++++++++++++++++ 5 files changed, 118 insertions(+), 3 deletions(-) create mode 100644 src/Command/SetCompatibilityModeForSchemaCommand.php create mode 100644 tests/Command/SetCompatibilityModeForSchemaCommandTest.php diff --git a/README.md b/README.md index 2646809..f7be8ff 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ composer require jobcloud/php-console-kafka-schema-registry ``` ## Requirements -- php: >= 7.4 +- php: ^8.0 ## Register commands You can register each command separately like this: diff --git a/docker/docker-compose.ci.yml b/docker/docker-compose.ci.yml index bf9f675..b94051c 100644 --- a/docker/docker-compose.ci.yml +++ b/docker/docker-compose.ci.yml @@ -1,4 +1,3 @@ -version: '3.2' services: php: build: diff --git a/docker/docker-compose.yml b/docker/docker-compose.yml index bf9f675..b94051c 100644 --- a/docker/docker-compose.yml +++ b/docker/docker-compose.yml @@ -1,4 +1,3 @@ -version: '3.2' services: php: build: diff --git a/src/Command/SetCompatibilityModeForSchemaCommand.php b/src/Command/SetCompatibilityModeForSchemaCommand.php new file mode 100644 index 0000000..2f65cc0 --- /dev/null +++ b/src/Command/SetCompatibilityModeForSchemaCommand.php @@ -0,0 +1,43 @@ +setName('kafka-schema-registry:set:schema:compatibility:mode') + ->setDescription('Set the compatibility mode for a given schema') + ->setHelp('Set the compatibility mode for a given schema') + ->addArgument('schemaName', InputArgument::REQUIRED, 'Name of the schema') + ->addArgument('compatibilityLevel', InputArgument::REQUIRED, 'Compatibility level to set'); + } + + public function execute(InputInterface $input, OutputInterface $output): int + { + $schemaName = (string) $input->getArgument('schemaName'); + $compatibilityLevel = (string) $input->getArgument('compatibilityLevel'); + + $result = $this->schemaRegistryApi->setSubjectCompatibilityLevel($schemaName, $compatibilityLevel); + + if (true !== $result) { + $output->writeln(sprintf('Could not change compatibility mode for schema: %s', $schemaName)); + + return 1; + } + + $output->writeln(sprintf('Successfully changed compatibility mode for schema: %s', $schemaName)); + + return 0; + } +} diff --git a/tests/Command/SetCompatibilityModeForSchemaCommandTest.php b/tests/Command/SetCompatibilityModeForSchemaCommandTest.php new file mode 100644 index 0000000..4c4b400 --- /dev/null +++ b/tests/Command/SetCompatibilityModeForSchemaCommandTest.php @@ -0,0 +1,74 @@ +makeMock(KafkaSchemaRegistryApiClient::class, [ + 'setSubjectCompatibilityLevel' => true, + ]); + + $application = new Application(); + $application->add(new SetCompatibilityModeForSchemaCommand($schemaRegistryApi)); + $command = $application->find('kafka-schema-registry:set:schema:compatibility:mode'); + $commandTester = new CommandTester($command); + + $commandTester->execute([ + 'schemaName' => $schemaName, + 'compatibilityLevel' => 'BACKWARD_TRANSITIVE', + ]); + + $commandOutput = trim($commandTester->getDisplay()); + + self::assertEquals( + sprintf('Successfully changed compatibility mode for schema: %s', $schemaName), + $commandOutput + ); + self::assertEquals(0, $commandTester->getStatusCode()); + } + + public function testCommandWhenCompatibilityIsNotChanged(): void + { + $schemaName = 'SomeSchemaName'; + + /** @var MockObject|KafkaSchemaRegistryApiClient $schemaRegistryApi */ + $schemaRegistryApi = $this->makeMock(KafkaSchemaRegistryApiClient::class, [ + 'setSubjectCompatibilityLevel' => false, + ]); + + $application = new Application(); + $application->add(new SetCompatibilityModeForSchemaCommand($schemaRegistryApi)); + $command = $application->find('kafka-schema-registry:set:schema:compatibility:mode'); + $commandTester = new CommandTester($command); + + $commandTester->execute([ + 'schemaName' => $schemaName, + 'compatibilityLevel' => 'BACKWARD_TRANSITIVE', + ]); + + $commandOutput = trim($commandTester->getDisplay()); + + self::assertEquals( + sprintf('Could not change compatibility mode for schema: %s', $schemaName), + $commandOutput + ); + self::assertEquals(1, $commandTester->getStatusCode()); + } +} From f0d2272a5533e4da9bf765a807a1383dea0d3fe8 Mon Sep 17 00:00:00 2001 From: Marko Date: Mon, 6 Oct 2025 11:36:42 +0200 Subject: [PATCH 2/9] Register command --- src/ServiceProvider/CommandServiceProvider.php | 2 ++ tests/Command/SetCompatibilityModeForSchemaCommandTest.php | 2 +- tests/ServiceProvider/CommandServiceProviderTest.php | 2 ++ 3 files changed, 5 insertions(+), 1 deletion(-) diff --git a/src/ServiceProvider/CommandServiceProvider.php b/src/ServiceProvider/CommandServiceProvider.php index aba8dff..7ebafe0 100644 --- a/src/ServiceProvider/CommandServiceProvider.php +++ b/src/ServiceProvider/CommandServiceProvider.php @@ -21,6 +21,7 @@ use Jobcloud\SchemaConsole\Command\ListVersionsForSchemaCommand; use Jobcloud\SchemaConsole\Command\RegisterChangedSchemasCommand; use Jobcloud\SchemaConsole\Command\RegisterSchemaVersionCommand; +use Jobcloud\SchemaConsole\Command\SetCompatibilityModeForSchemaCommand; use Jobcloud\SchemaConsole\Command\SetImportModeCommand; use Jobcloud\SchemaConsole\Command\SetReadOnlyModeCommand; use Jobcloud\SchemaConsole\Command\SetReadWriteModeCommand; @@ -51,6 +52,7 @@ public function register(Container $container) new GetCompatibilityModeCommand($schemaRegistryApi), new CheckAllSchemasCompatibilityCommand($schemaRegistryApi), new GetCompatibilityModeForSchemaCommand($schemaRegistryApi), + new SetCompatibilityModeForSchemaCommand($schemaRegistryApi), new GetLatestSchemaCommand($schemaRegistryApi), new GetSchemaByVersionCommand($schemaRegistryApi), new ListAllSchemasCommand($schemaRegistryApi), diff --git a/tests/Command/SetCompatibilityModeForSchemaCommandTest.php b/tests/Command/SetCompatibilityModeForSchemaCommandTest.php index 4c4b400..9c04671 100644 --- a/tests/Command/SetCompatibilityModeForSchemaCommandTest.php +++ b/tests/Command/SetCompatibilityModeForSchemaCommandTest.php @@ -10,7 +10,7 @@ use Symfony\Component\Console\Tester\CommandTester; /** - * @covers \Jobcloud\SchemaConsole\Command\GetCompatibilityModeForSchemaCommand + * @covers \Jobcloud\SchemaConsole\Command\SetCompatibilityModeForSchemaCommand * @covers \Jobcloud\SchemaConsole\Helper\SchemaFileHelper * @covers \Jobcloud\SchemaConsole\Command\AbstractSchemaCommand */ diff --git a/tests/ServiceProvider/CommandServiceProviderTest.php b/tests/ServiceProvider/CommandServiceProviderTest.php index fa982c6..eece265 100644 --- a/tests/ServiceProvider/CommandServiceProviderTest.php +++ b/tests/ServiceProvider/CommandServiceProviderTest.php @@ -20,6 +20,7 @@ use Jobcloud\SchemaConsole\Command\ListVersionsForSchemaCommand; use Jobcloud\SchemaConsole\Command\RegisterChangedSchemasCommand; use Jobcloud\SchemaConsole\Command\RegisterSchemaVersionCommand; +use Jobcloud\SchemaConsole\Command\SetCompatibilityModeForSchemaCommand; use Jobcloud\SchemaConsole\Command\SetImportModeCommand; use Jobcloud\SchemaConsole\Command\SetReadOnlyModeCommand; use Jobcloud\SchemaConsole\Command\SetReadWriteModeCommand; @@ -83,6 +84,7 @@ public function testHasAllOfTheCommands(): void self::assertArrayHasInstanceOf(DeleteAllSchemasCommand::class, $commands); self::assertArrayHasInstanceOf(GetCompatibilityModeCommand::class, $commands); self::assertArrayHasInstanceOf(GetCompatibilityModeForSchemaCommand::class, $commands); + self::assertArrayHasInstanceOf(SetCompatibilityModeForSchemaCommand::class, $commands); self::assertArrayHasInstanceOf(GetLatestSchemaCommand::class, $commands); self::assertArrayHasInstanceOf(GetSchemaByVersionCommand::class, $commands); self::assertArrayHasInstanceOf(ListAllSchemasCommand::class, $commands); From d67aed30ae89bc800bd173f217757b37f267e399 Mon Sep 17 00:00:00 2001 From: Marko Date: Mon, 6 Oct 2025 17:16:03 +0200 Subject: [PATCH 3/9] Add command to change compatibility for all schemas from config --- .../SetAllSchemasCompatibilityModeCommand.php | 156 ++++++++++++++++++ .../CommandServiceProvider.php | 2 + 2 files changed, 158 insertions(+) create mode 100644 src/Command/SetAllSchemasCompatibilityModeCommand.php diff --git a/src/Command/SetAllSchemasCompatibilityModeCommand.php b/src/Command/SetAllSchemasCompatibilityModeCommand.php new file mode 100644 index 0000000..b928c40 --- /dev/null +++ b/src/Command/SetAllSchemasCompatibilityModeCommand.php @@ -0,0 +1,156 @@ +setName('kafka-schema-registry:set:compatibility:mode:all') + ->setDescription('Set compatibility modes for multiple schemas from a JSON configuration file') + ->setHelp($this->getHelpText()) + ->addArgument( + 'configFile', + InputArgument::REQUIRED, 'Path to JSON configuration file containing schema-compatibility mappings' + ); + } + + private function getHelpText(): string + { + return <<<'HELP' +Set compatibility modes for multiple schemas based on configuration in a JSON file. + +JSON File Format: +The configuration file must be a valid JSON file with the following structure: + +{ + "schemas": [ + { + "schemaName": "schema-subject-name", + "compatibilityLevel": "COMPATIBILITY_LEVEL" + } + ] +} + +Required Fields: +- schemas: Array of schema configuration objects +- schemaName: The subject name of the schema in the registry +- compatibilityLevel: One of the following compatibility levels: + * NONE + * BACKWARD + * BACKWARD_TRANSITIVE + * FORWARD + * FORWARD_TRANSITIVE + * FULL + * FULL_TRANSITIVE + +Example: +{ + "schemas": [ + { + "schemaName": "user-events", + "compatibilityLevel": "BACKWARD_TRANSITIVE" + }, + { + "schemaName": "order-events", + "compatibilityLevel": "FORWARD" + }, + { + "schemaName": "payment-events", + "compatibilityLevel": "FULL" + } + ] +} + +The command will process each schema in the order specified and provide feedback +for each operation. If any schema update fails, the command will continue processing +the remaining schemas and return a non-zero exit code at the end. +HELP; + } + + public function execute(InputInterface $input, OutputInterface $output): int + { + $configFilePath = (string) $input->getArgument('configFile'); + + if (false === file_exists($configFilePath)) { + $output->writeln(sprintf('Configuration file not found: %s', $configFilePath)); + + return 1; + } + + $jsonContent = file_get_contents($configFilePath); + if (false === $jsonContent) { + $output->writeln(sprintf('Could not read configuration file: %s', $configFilePath)); + + return 1; + } + + $config = json_decode($jsonContent, true); + if (null === $config) { + $output->writeln(sprintf('Invalid JSON in configuration file: %s', $configFilePath)); + $output->writeln(sprintf('JSON Error: %s', json_last_error_msg())); + + return 1; + } + + if (false === isset($config['schemas']) || false === is_array($config['schemas'])) { + $output->writeln('Configuration file must contain a "schemas" array'); + + return 1; + } + + $totalSchemas = count($config['schemas']); + $successCount = 0; + $failureCount = 0; + + $output->writeln(sprintf('Processing %d schema compatibility configurations...', $totalSchemas)); + + foreach ($config['schemas'] as $index => $schemaConfig) { + if (false === isset($schemaConfig['schemaName']) || false === isset($schemaConfig['compatibilityLevel'])) { + $output->writeln( + sprintf('Invalid configuration at index %d: missing schemaName or compatibilityLevel', $index) + ); + $failureCount++; + + continue; + } + + $schemaName = (string) $schemaConfig['schemaName']; + $compatibilityLevel = (string) $schemaConfig['compatibilityLevel']; + + $output->write( + sprintf('Setting compatibility mode for schema "%s" to "%s"... ', $schemaName, $compatibilityLevel) + ); + + try { + $result = $this->schemaRegistryApi->setSubjectCompatibilityLevel($schemaName, $compatibilityLevel); + + if (true === $result) { + $output->writeln('SUCCESS'); + $successCount++; + } else { + $output->writeln('FAILED'); + $failureCount++; + } + } catch (\Exception $e) { + $output->writeln(sprintf('FAILED: %s', $e->getMessage())); + $failureCount++; + } + } + + $output->writeln(''); + $output->writeln('=== Summary ==='); + $output->writeln(sprintf('Total schemas processed: %d', $totalSchemas)); + $output->writeln(sprintf('Successful updates: %d', $successCount)); + $output->writeln(sprintf('Failed updates: %d', $failureCount)); + + return $failureCount > 0 ? 1 : 0; + } +} diff --git a/src/ServiceProvider/CommandServiceProvider.php b/src/ServiceProvider/CommandServiceProvider.php index 7ebafe0..e96c6b9 100644 --- a/src/ServiceProvider/CommandServiceProvider.php +++ b/src/ServiceProvider/CommandServiceProvider.php @@ -21,6 +21,7 @@ use Jobcloud\SchemaConsole\Command\ListVersionsForSchemaCommand; use Jobcloud\SchemaConsole\Command\RegisterChangedSchemasCommand; use Jobcloud\SchemaConsole\Command\RegisterSchemaVersionCommand; +use Jobcloud\SchemaConsole\Command\SetAllSchemasCompatibilityModeCommand; use Jobcloud\SchemaConsole\Command\SetCompatibilityModeForSchemaCommand; use Jobcloud\SchemaConsole\Command\SetImportModeCommand; use Jobcloud\SchemaConsole\Command\SetReadOnlyModeCommand; @@ -53,6 +54,7 @@ public function register(Container $container) new CheckAllSchemasCompatibilityCommand($schemaRegistryApi), new GetCompatibilityModeForSchemaCommand($schemaRegistryApi), new SetCompatibilityModeForSchemaCommand($schemaRegistryApi), + new SetAllSchemasCompatibilityModeCommand($schemaRegistryApi), new GetLatestSchemaCommand($schemaRegistryApi), new GetSchemaByVersionCommand($schemaRegistryApi), new ListAllSchemasCommand($schemaRegistryApi), From 53462f3137741a2c678780ee84e901965c91d6b3 Mon Sep 17 00:00:00 2001 From: Marko Date: Tue, 7 Oct 2025 14:32:15 +0200 Subject: [PATCH 4/9] Update command --- .../SetAllSchemasCompatibilityModeCommand.php | 71 +++++++++++-------- .../CommandServiceProvider.php | 2 +- .../CommandServiceProviderTest.php | 4 ++ 3 files changed, 45 insertions(+), 32 deletions(-) diff --git a/src/Command/SetAllSchemasCompatibilityModeCommand.php b/src/Command/SetAllSchemasCompatibilityModeCommand.php index b928c40..347ed27 100644 --- a/src/Command/SetAllSchemasCompatibilityModeCommand.php +++ b/src/Command/SetAllSchemasCompatibilityModeCommand.php @@ -28,19 +28,16 @@ private function getHelpText(): string Set compatibility modes for multiple schemas based on configuration in a JSON file. JSON File Format: -The configuration file must be a valid JSON file with the following structure: +The configuration file must be a valid JSON array with the following structure: -{ - "schemas": [ - { - "schemaName": "schema-subject-name", - "compatibilityLevel": "COMPATIBILITY_LEVEL" - } - ] -} +[ + { + "schemaName": "schema-subject-name", + "compatibilityLevel": "COMPATIBILITY_LEVEL" + } +] Required Fields: -- schemas: Array of schema configuration objects - schemaName: The subject name of the schema in the registry - compatibilityLevel: One of the following compatibility levels: * NONE @@ -52,22 +49,20 @@ private function getHelpText(): string * FULL_TRANSITIVE Example: -{ - "schemas": [ - { - "schemaName": "user-events", - "compatibilityLevel": "BACKWARD_TRANSITIVE" - }, - { - "schemaName": "order-events", - "compatibilityLevel": "FORWARD" - }, - { - "schemaName": "payment-events", - "compatibilityLevel": "FULL" - } - ] -} +[ + { + "schemaName": "user-events", + "compatibilityLevel": "BACKWARD_TRANSITIVE" + }, + { + "schemaName": "order-events", + "compatibilityLevel": "FORWARD" + }, + { + "schemaName": "payment-events", + "compatibilityLevel": "FULL" + } +] The command will process each schema in the order specified and provide feedback for each operation. If any schema update fails, the command will continue processing @@ -85,7 +80,20 @@ public function execute(InputInterface $input, OutputInterface $output): int return 1; } - $jsonContent = file_get_contents($configFilePath); + // Check if path is actually a file (not a directory) + if (false === is_file($configFilePath)) { + $output->writeln(sprintf('Could not read configuration file: %s', $configFilePath)); + + return 1; + } + + if (false === is_readable($configFilePath)) { + $output->writeln(sprintf('Could not read configuration file: %s', $configFilePath)); + + return 1; + } + + $jsonContent = @file_get_contents($configFilePath); if (false === $jsonContent) { $output->writeln(sprintf('Could not read configuration file: %s', $configFilePath)); @@ -100,19 +108,20 @@ public function execute(InputInterface $input, OutputInterface $output): int return 1; } - if (false === isset($config['schemas']) || false === is_array($config['schemas'])) { - $output->writeln('Configuration file must contain a "schemas" array'); + // Validate that config is an array + if (false === is_array($config)) { + $output->writeln('Configuration file must contain a JSON array of schema configurations'); return 1; } - $totalSchemas = count($config['schemas']); + $totalSchemas = count($config); $successCount = 0; $failureCount = 0; $output->writeln(sprintf('Processing %d schema compatibility configurations...', $totalSchemas)); - foreach ($config['schemas'] as $index => $schemaConfig) { + foreach ($config as $index => $schemaConfig) { if (false === isset($schemaConfig['schemaName']) || false === isset($schemaConfig['compatibilityLevel'])) { $output->writeln( sprintf('Invalid configuration at index %d: missing schemaName or compatibilityLevel', $index) diff --git a/src/ServiceProvider/CommandServiceProvider.php b/src/ServiceProvider/CommandServiceProvider.php index e96c6b9..2792154 100644 --- a/src/ServiceProvider/CommandServiceProvider.php +++ b/src/ServiceProvider/CommandServiceProvider.php @@ -53,8 +53,8 @@ public function register(Container $container) new GetCompatibilityModeCommand($schemaRegistryApi), new CheckAllSchemasCompatibilityCommand($schemaRegistryApi), new GetCompatibilityModeForSchemaCommand($schemaRegistryApi), - new SetCompatibilityModeForSchemaCommand($schemaRegistryApi), new SetAllSchemasCompatibilityModeCommand($schemaRegistryApi), + new SetCompatibilityModeForSchemaCommand($schemaRegistryApi), new GetLatestSchemaCommand($schemaRegistryApi), new GetSchemaByVersionCommand($schemaRegistryApi), new ListAllSchemasCommand($schemaRegistryApi), diff --git a/tests/ServiceProvider/CommandServiceProviderTest.php b/tests/ServiceProvider/CommandServiceProviderTest.php index eece265..7af3997 100644 --- a/tests/ServiceProvider/CommandServiceProviderTest.php +++ b/tests/ServiceProvider/CommandServiceProviderTest.php @@ -20,6 +20,7 @@ use Jobcloud\SchemaConsole\Command\ListVersionsForSchemaCommand; use Jobcloud\SchemaConsole\Command\RegisterChangedSchemasCommand; use Jobcloud\SchemaConsole\Command\RegisterSchemaVersionCommand; +use Jobcloud\SchemaConsole\Command\SetAllSchemasCompatibilityModeCommand; use Jobcloud\SchemaConsole\Command\SetCompatibilityModeForSchemaCommand; use Jobcloud\SchemaConsole\Command\SetImportModeCommand; use Jobcloud\SchemaConsole\Command\SetReadOnlyModeCommand; @@ -84,6 +85,7 @@ public function testHasAllOfTheCommands(): void self::assertArrayHasInstanceOf(DeleteAllSchemasCommand::class, $commands); self::assertArrayHasInstanceOf(GetCompatibilityModeCommand::class, $commands); self::assertArrayHasInstanceOf(GetCompatibilityModeForSchemaCommand::class, $commands); + self::assertArrayHasInstanceOf(SetAllSchemasCompatibilityModeCommand::class, $commands); self::assertArrayHasInstanceOf(SetCompatibilityModeForSchemaCommand::class, $commands); self::assertArrayHasInstanceOf(GetLatestSchemaCommand::class, $commands); self::assertArrayHasInstanceOf(GetSchemaByVersionCommand::class, $commands); @@ -99,5 +101,7 @@ public function testHasAllOfTheCommands(): void self::assertArrayHasInstanceOf(CheckAllSchemaTemplatesDocCommentsCommand::class, $commands); self::assertArrayHasInstanceOf(CheckAllSchemaTemplatesDefaultTypeCommand::class, $commands); self::assertArrayHasInstanceOf(CheckAllSchemaTemplatesNamesCommand::class, $commands); + + self::assertCount(22, $commands); } } From 9cec16ccb1603ed9424ac4eef6aa5e2aafc448bb Mon Sep 17 00:00:00 2001 From: Marko Date: Tue, 7 Oct 2025 15:31:57 +0200 Subject: [PATCH 5/9] Update command --- .../SetAllSchemasCompatibilityModeCommand.php | 120 +++--- ...AllSchemasCompatibilityModeCommandTest.php | 342 ++++++++++++++++++ 2 files changed, 406 insertions(+), 56 deletions(-) create mode 100644 tests/Command/SetAllSchemasCompatibilityModeCommandTest.php diff --git a/src/Command/SetAllSchemasCompatibilityModeCommand.php b/src/Command/SetAllSchemasCompatibilityModeCommand.php index 347ed27..9b6bc0c 100644 --- a/src/Command/SetAllSchemasCompatibilityModeCommand.php +++ b/src/Command/SetAllSchemasCompatibilityModeCommand.php @@ -74,44 +74,8 @@ public function execute(InputInterface $input, OutputInterface $output): int { $configFilePath = (string) $input->getArgument('configFile'); - if (false === file_exists($configFilePath)) { - $output->writeln(sprintf('Configuration file not found: %s', $configFilePath)); - - return 1; - } - - // Check if path is actually a file (not a directory) - if (false === is_file($configFilePath)) { - $output->writeln(sprintf('Could not read configuration file: %s', $configFilePath)); - - return 1; - } - - if (false === is_readable($configFilePath)) { - $output->writeln(sprintf('Could not read configuration file: %s', $configFilePath)); - - return 1; - } - - $jsonContent = @file_get_contents($configFilePath); - if (false === $jsonContent) { - $output->writeln(sprintf('Could not read configuration file: %s', $configFilePath)); - - return 1; - } - - $config = json_decode($jsonContent, true); + $config = $this->loadConfigFile($configFilePath, $output); if (null === $config) { - $output->writeln(sprintf('Invalid JSON in configuration file: %s', $configFilePath)); - $output->writeln(sprintf('JSON Error: %s', json_last_error_msg())); - - return 1; - } - - // Validate that config is an array - if (false === is_array($config)) { - $output->writeln('Configuration file must contain a JSON array of schema configurations'); - return 1; } @@ -122,7 +86,7 @@ public function execute(InputInterface $input, OutputInterface $output): int $output->writeln(sprintf('Processing %d schema compatibility configurations...', $totalSchemas)); foreach ($config as $index => $schemaConfig) { - if (false === isset($schemaConfig['schemaName']) || false === isset($schemaConfig['compatibilityLevel'])) { + if (false === $this->isValidSchemaConfig($schemaConfig)) { $output->writeln( sprintf('Invalid configuration at index %d: missing schemaName or compatibilityLevel', $index) ); @@ -138,28 +102,72 @@ public function execute(InputInterface $input, OutputInterface $output): int sprintf('Setting compatibility mode for schema "%s" to "%s"... ', $schemaName, $compatibilityLevel) ); - try { - $result = $this->schemaRegistryApi->setSubjectCompatibilityLevel($schemaName, $compatibilityLevel); - - if (true === $result) { - $output->writeln('SUCCESS'); - $successCount++; - } else { - $output->writeln('FAILED'); - $failureCount++; - } - } catch (\Exception $e) { - $output->writeln(sprintf('FAILED: %s', $e->getMessage())); - $failureCount++; + if ($this->setSchemaCompatibility($schemaName, $compatibilityLevel, $output)) { + $successCount++; + + continue; } + + $failureCount++; } - $output->writeln(''); - $output->writeln('=== Summary ==='); - $output->writeln(sprintf('Total schemas processed: %d', $totalSchemas)); - $output->writeln(sprintf('Successful updates: %d', $successCount)); - $output->writeln(sprintf('Failed updates: %d', $failureCount)); + $this->outputSummary($output, $totalSchemas, $successCount, $failureCount); return $failureCount > 0 ? 1 : 0; } + + private function loadConfigFile(string $configFilePath, OutputInterface $output): ?array + { + $jsonContent = @file_get_contents($configFilePath); + if (false === $jsonContent) { + $output->writeln(sprintf('Could not read configuration file: %s', $configFilePath)); + + return null; + } + + $config = json_decode($jsonContent, true); + if (null === $config || false === is_array($config) || false === array_is_list($config)) { + $output->writeln('Configuration file must contain a JSON array of schema configurations'); + + return null; + } + + return $config; + } + + private function isValidSchemaConfig(mixed $schemaConfig): bool + { + return is_array($schemaConfig) + && isset($schemaConfig['schemaName']) + && isset($schemaConfig['compatibilityLevel']); + } + + private function setSchemaCompatibility(string $schemaName, string $compatibilityLevel, OutputInterface $output): bool + { + try { + $result = $this->schemaRegistryApi->setSubjectCompatibilityLevel($schemaName, $compatibilityLevel); + + if (true === $result) { + $output->writeln('SUCCESS'); + return true; + } + + $output->writeln('FAILED'); + + return false; + } catch (\Exception $e) { + $output->writeln(sprintf('FAILED: %s', $e->getMessage())); + + return false; + } + } + + private function outputSummary(OutputInterface $output, int $total, int $success, int $failure): void + { + $output->writeln(''); + $output->writeln('=== Summary ==='); + $output->writeln(sprintf('Total schemas processed: %d', $total)); + $output->writeln(sprintf('Successful updates: %d', $success)); + $output->writeln(sprintf('Failed updates: %d', $failure)); + } } diff --git a/tests/Command/SetAllSchemasCompatibilityModeCommandTest.php b/tests/Command/SetAllSchemasCompatibilityModeCommandTest.php new file mode 100644 index 0000000..f3d0be3 --- /dev/null +++ b/tests/Command/SetAllSchemasCompatibilityModeCommandTest.php @@ -0,0 +1,342 @@ +createTempConfigFile([ + ['schemaName' => 'schema1', 'compatibilityLevel' => 'BACKWARD'], + ['schemaName' => 'schema2', 'compatibilityLevel' => 'FORWARD'], + ]); + + /** @var MockObject|KafkaSchemaRegistryApiClient $schemaRegistryApi */ + $schemaRegistryApi = $this->makeMock(KafkaSchemaRegistryApiClient::class, [ + 'setSubjectCompatibilityLevel' => true, + ]); + + $commandTester = $this->createCommandTester($schemaRegistryApi); + $commandTester->execute(['configFile' => $configFile]); + + $output = $commandTester->getDisplay(); + + self::assertStringContainsString('Processing 2 schema compatibility configurations...', $output); + self::assertStringContainsString( + 'Setting compatibility mode for schema "schema1" to "BACKWARD"... SUCCESS', + $output + ); + self::assertStringContainsString( + 'Setting compatibility mode for schema "schema2" to "FORWARD"... SUCCESS', + $output + ); + self::assertStringContainsString('Total schemas processed: 2', $output); + self::assertStringContainsString('Successful updates: 2', $output); + self::assertStringContainsString('Failed updates: 0', $output); + self::assertEquals(0, $commandTester->getStatusCode()); + + unlink($configFile); + } + + public function testCommandWithValidConfigFileAndSomeFailures(): void + { + $configFile = $this->createTempConfigFile([ + ['schemaName' => 'schema1', 'compatibilityLevel' => 'BACKWARD'], + ['schemaName' => 'schema2', 'compatibilityLevel' => 'FORWARD'], + ['schemaName' => 'schema3', 'compatibilityLevel' => 'FULL'], + ]); + + /** @var MockObject|KafkaSchemaRegistryApiClient $schemaRegistryApi */ + $schemaRegistryApi = $this->makeMock(KafkaSchemaRegistryApiClient::class); + $schemaRegistryApi->method('setSubjectCompatibilityLevel') + ->willReturnOnConsecutiveCalls(true, false, true); + + $commandTester = $this->createCommandTester($schemaRegistryApi); + $commandTester->execute(['configFile' => $configFile]); + + $output = $commandTester->getDisplay(); + + self::assertStringContainsString('Processing 3 schema compatibility configurations...', $output); + self::assertStringContainsString( + 'Setting compatibility mode for schema "schema1" to "BACKWARD"... SUCCESS', + $output + ); + self::assertStringContainsString( + 'Setting compatibility mode for schema "schema2" to "FORWARD"... FAILED', + $output + ); + self::assertStringContainsString( + 'Setting compatibility mode for schema "schema3" to "FULL"... SUCCESS', + $output + ); + self::assertStringContainsString('Total schemas processed: 3', $output); + self::assertStringContainsString('Successful updates: 2', $output); + self::assertStringContainsString('Failed updates: 1', $output); + self::assertEquals(1, $commandTester->getStatusCode()); + + unlink($configFile); + } + + public function testCommandWithApiException(): void + { + $configFile = $this->createTempConfigFile([ + ['schemaName' => 'schema1', 'compatibilityLevel' => 'BACKWARD'], + ['schemaName' => 'schema2', 'compatibilityLevel' => 'FORWARD'], + ]); + + /** @var MockObject|KafkaSchemaRegistryApiClient $schemaRegistryApi */ + $schemaRegistryApi = $this->makeMock(KafkaSchemaRegistryApiClient::class); + $schemaRegistryApi->method('setSubjectCompatibilityLevel') + ->willReturnOnConsecutiveCalls( + true, + $this->throwException(new \Exception('API Error: Schema not found')) + ); + + $commandTester = $this->createCommandTester($schemaRegistryApi); + $commandTester->execute(['configFile' => $configFile]); + + $output = $commandTester->getDisplay(); + + self::assertStringContainsString( + 'Setting compatibility mode for schema "schema1" to "BACKWARD"... SUCCESS', + $output + ); + self::assertStringContainsString( + 'Setting compatibility mode for schema "schema2" to "FORWARD"... FAILED: API Error: Schema not found', + $output + ); + self::assertStringContainsString('Successful updates: 1', $output); + self::assertStringContainsString('Failed updates: 1', $output); + self::assertEquals(1, $commandTester->getStatusCode()); + + unlink($configFile); + } + + public function testCommandWithNonExistentConfigFile(): void + { + /** @var MockObject|KafkaSchemaRegistryApiClient $schemaRegistryApi */ + $schemaRegistryApi = $this->makeMock(KafkaSchemaRegistryApiClient::class); + + $commandTester = $this->createCommandTester($schemaRegistryApi); + $commandTester->execute(['configFile' => '/non/existent/file.json']); + + $output = $commandTester->getDisplay(); + + self::assertStringContainsString('Could not read configuration file: /non/existent/file.json', $output); + self::assertEquals(1, $commandTester->getStatusCode()); + } + + /** + * @dataProvider invalidJsonConfigurationProvider + */ + public function testCommandWithInvalidJsonConfiguration(mixed $configData): void + { + if (is_string($configData)) { + // For raw string data (like invalid JSON), write directly to file + $configFile = tempnam(sys_get_temp_dir(), 'test_config'); + file_put_contents($configFile, $configData); + + /** @var MockObject|KafkaSchemaRegistryApiClient $schemaRegistryApi */ + $schemaRegistryApi = $this->makeMock(KafkaSchemaRegistryApiClient::class); + + $commandTester = $this->createCommandTester($schemaRegistryApi); + $commandTester->execute(['configFile' => $configFile]); + + $output = $commandTester->getDisplay(); + + self::assertStringContainsString( + 'Configuration file must contain a JSON array of schema configurations', + $output + ); + self::assertEquals(1, $commandTester->getStatusCode()); + + unlink($configFile); + + return; + } + + $configFile = $this->createTempConfigFile($configData); + + /** @var MockObject|KafkaSchemaRegistryApiClient $schemaRegistryApi */ + $schemaRegistryApi = $this->makeMock(KafkaSchemaRegistryApiClient::class); + + $commandTester = $this->createCommandTester($schemaRegistryApi); + $commandTester->execute(['configFile' => $configFile]); + + $output = $commandTester->getDisplay(); + + self::assertStringContainsString( + 'Configuration file must contain a JSON array of schema configurations', + $output + ); + self::assertEquals(1, $commandTester->getStatusCode()); + + unlink($configFile); + } + + public static function invalidJsonConfigurationProvider(): array + { + return [ + 'invalidJsonConfigurationProvider:1' => ['[invalid json}'], + 'invalidJsonConfigurationProvider:2' => [''], + 'invalidJsonConfigurationProvider:3' => [['invalid' => 'structure']], + 'invalidJsonConfigurationProvider:4' => ['not_an_array'], + 'invalidJsonConfigurationProvider:5' => [null], + 'invalidJsonConfigurationProvider:6' => [true], + 'invalidJsonConfigurationProvider:7' => [123], + ]; + } + + public function testCommandWithMissingSchemaNameField(): void + { + $configFile = $this->createTempConfigFile([ + ['compatibilityLevel' => 'BACKWARD'], + ['schemaName' => 'valid-schema', 'compatibilityLevel' => 'FORWARD'], + ]); + + /** @var MockObject|KafkaSchemaRegistryApiClient $schemaRegistryApi */ + $schemaRegistryApi = $this->makeMock(KafkaSchemaRegistryApiClient::class, [ + 'setSubjectCompatibilityLevel' => true, + ]); + + $commandTester = $this->createCommandTester($schemaRegistryApi); + $commandTester->execute(['configFile' => $configFile]); + + $output = $commandTester->getDisplay(); + + self::assertStringContainsString( + 'Invalid configuration at index 0: missing schemaName or compatibilityLevel', + $output + ); + self::assertStringContainsString( + 'Setting compatibility mode for schema "valid-schema" to "FORWARD"... SUCCESS', + $output + ); + self::assertStringContainsString('Successful updates: 1', $output); + self::assertStringContainsString('Failed updates: 1', $output); + self::assertEquals(1, $commandTester->getStatusCode()); + + unlink($configFile); + } + + public function testCommandWithMissingCompatibilityLevelField(): void + { + $configFile = $this->createTempConfigFile([ + ['schemaName' => 'incomplete-schema'], + ['schemaName' => 'valid-schema', 'compatibilityLevel' => 'BACKWARD'], + ]); + + /** @var MockObject|KafkaSchemaRegistryApiClient $schemaRegistryApi */ + $schemaRegistryApi = $this->makeMock(KafkaSchemaRegistryApiClient::class, [ + 'setSubjectCompatibilityLevel' => true, + ]); + + $commandTester = $this->createCommandTester($schemaRegistryApi); + $commandTester->execute(['configFile' => $configFile]); + + $output = $commandTester->getDisplay(); + + self::assertStringContainsString( + 'Invalid configuration at index 0: missing schemaName or compatibilityLevel', + $output + ); + self::assertStringContainsString( + 'Setting compatibility mode for schema "valid-schema" to "BACKWARD"... SUCCESS', + $output + ); + self::assertStringContainsString('Successful updates: 1', $output); + self::assertStringContainsString('Failed updates: 1', $output); + self::assertEquals(1, $commandTester->getStatusCode()); + + unlink($configFile); + } + + public function testCommandWithEmptyArray(): void + { + $configFile = $this->createTempConfigFile([]); + + /** @var MockObject|KafkaSchemaRegistryApiClient $schemaRegistryApi */ + $schemaRegistryApi = $this->makeMock(KafkaSchemaRegistryApiClient::class); + + $commandTester = $this->createCommandTester($schemaRegistryApi); + $commandTester->execute(['configFile' => $configFile]); + + $output = $commandTester->getDisplay(); + + self::assertStringContainsString('Processing 0 schema compatibility configurations...', $output); + self::assertStringContainsString('Total schemas processed: 0', $output); + self::assertStringContainsString('Successful updates: 0', $output); + self::assertStringContainsString('Failed updates: 0', $output); + self::assertEquals(0, $commandTester->getStatusCode()); + + unlink($configFile); + } + + public function testCommandWithAllValidCompatibilityLevels(): void + { + $compatibilityLevels = [ + 'NONE', 'BACKWARD', 'BACKWARD_TRANSITIVE', + 'FORWARD', 'FORWARD_TRANSITIVE', 'FULL', 'FULL_TRANSITIVE', + ]; + + $schemas = []; + foreach ($compatibilityLevels as $level) { + $schemas[] = ['schemaName' => "schema-{$level}", 'compatibilityLevel' => $level]; + } + + $configFile = $this->createTempConfigFile($schemas); + + /** @var MockObject|KafkaSchemaRegistryApiClient $schemaRegistryApi */ + $schemaRegistryApi = $this->makeMock(KafkaSchemaRegistryApiClient::class, [ + 'setSubjectCompatibilityLevel' => true, + ]); + + $commandTester = $this->createCommandTester($schemaRegistryApi); + $commandTester->execute(['configFile' => $configFile]); + + $output = $commandTester->getDisplay(); + + self::assertStringContainsString('Processing 7 schema compatibility configurations...', $output); + + foreach ($compatibilityLevels as $level) { + self::assertStringContainsString( + "Setting compatibility mode for schema \"schema-{$level}\" to \"{$level}\"... SUCCESS", + $output + ); + } + + self::assertStringContainsString('Successful updates: 7', $output); + self::assertStringContainsString('Failed updates: 0', $output); + self::assertEquals(0, $commandTester->getStatusCode()); + + unlink($configFile); + } + + private function createCommandTester(MockObject $schemaRegistryApi): CommandTester + { + $application = new Application(); + $application->add(new SetAllSchemasCompatibilityModeCommand($schemaRegistryApi)); + $command = $application->find('kafka-schema-registry:set:compatibility:mode:all'); + + return new CommandTester($command); + } + + private function createTempConfigFile(mixed $config): string + { + $configFile = tempnam(sys_get_temp_dir(), 'test_config'); + file_put_contents($configFile, json_encode($config)); + + return $configFile; + } +} From 3073e5b8fa389e3b9ff6351e6e70af9a3b6b0cf2 Mon Sep 17 00:00:00 2001 From: Marko Date: Tue, 7 Oct 2025 15:37:15 +0200 Subject: [PATCH 6/9] Fix pipeline --- .../SetAllSchemasCompatibilityModeCommand.php | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/src/Command/SetAllSchemasCompatibilityModeCommand.php b/src/Command/SetAllSchemasCompatibilityModeCommand.php index 9b6bc0c..5ff5b67 100644 --- a/src/Command/SetAllSchemasCompatibilityModeCommand.php +++ b/src/Command/SetAllSchemasCompatibilityModeCommand.php @@ -18,7 +18,8 @@ protected function configure(): void ->setHelp($this->getHelpText()) ->addArgument( 'configFile', - InputArgument::REQUIRED, 'Path to JSON configuration file containing schema-compatibility mappings' + InputArgument::REQUIRED, + 'Path to JSON configuration file containing schema-compatibility mappings' ); } @@ -116,6 +117,9 @@ public function execute(InputInterface $input, OutputInterface $output): int return $failureCount > 0 ? 1 : 0; } + /** + * @return array>|null + */ private function loadConfigFile(string $configFilePath, OutputInterface $output): ?array { $jsonContent = @file_get_contents($configFilePath); @@ -142,8 +146,11 @@ private function isValidSchemaConfig(mixed $schemaConfig): bool && isset($schemaConfig['compatibilityLevel']); } - private function setSchemaCompatibility(string $schemaName, string $compatibilityLevel, OutputInterface $output): bool - { + private function setSchemaCompatibility( + string $schemaName, + string $compatibilityLevel, + OutputInterface $output + ): bool { try { $result = $this->schemaRegistryApi->setSubjectCompatibilityLevel($schemaName, $compatibilityLevel); From 44001e01f17070a254072ff60291dfa2020db02c Mon Sep 17 00:00:00 2001 From: Marko Date: Tue, 7 Oct 2025 15:44:53 +0200 Subject: [PATCH 7/9] Adjustments --- tests/Command/SetAllSchemasCompatibilityModeCommandTest.php | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/tests/Command/SetAllSchemasCompatibilityModeCommandTest.php b/tests/Command/SetAllSchemasCompatibilityModeCommandTest.php index f3d0be3..3270da7 100644 --- a/tests/Command/SetAllSchemasCompatibilityModeCommandTest.php +++ b/tests/Command/SetAllSchemasCompatibilityModeCommandTest.php @@ -143,7 +143,6 @@ public function testCommandWithNonExistentConfigFile(): void public function testCommandWithInvalidJsonConfiguration(mixed $configData): void { if (is_string($configData)) { - // For raw string data (like invalid JSON), write directly to file $configFile = tempnam(sys_get_temp_dir(), 'test_config'); file_put_contents($configFile, $configData); @@ -185,6 +184,9 @@ public function testCommandWithInvalidJsonConfiguration(mixed $configData): void unlink($configFile); } + /** + * @return array + */ public static function invalidJsonConfigurationProvider(): array { return [ From 51d5f043169d16cacfed69df2338583e5a2e9caf Mon Sep 17 00:00:00 2001 From: Marko Date: Thu, 9 Oct 2025 11:46:43 +0200 Subject: [PATCH 8/9] Improvements --- .../SetAllSchemasCompatibilityModeCommand.php | 24 ++-- .../SetCompatibilityModeForSchemaCommand.php | 18 +-- ...AllSchemasCompatibilityModeCommandTest.php | 103 ++++++------------ ...tCompatibilityModeForSchemaCommandTest.php | 5 +- 4 files changed, 56 insertions(+), 94 deletions(-) diff --git a/src/Command/SetAllSchemasCompatibilityModeCommand.php b/src/Command/SetAllSchemasCompatibilityModeCommand.php index 5ff5b67..266e81e 100644 --- a/src/Command/SetAllSchemasCompatibilityModeCommand.php +++ b/src/Command/SetAllSchemasCompatibilityModeCommand.php @@ -4,6 +4,7 @@ namespace Jobcloud\SchemaConsole\Command; +use Symfony\Component\Console\Command\Command; use Symfony\Component\Console\Input\InputArgument; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Output\OutputInterface; @@ -77,7 +78,7 @@ public function execute(InputInterface $input, OutputInterface $output): int $config = $this->loadConfigFile($configFilePath, $output); if (null === $config) { - return 1; + return Command::FAILURE; } $totalSchemas = count($config); @@ -96,8 +97,8 @@ public function execute(InputInterface $input, OutputInterface $output): int continue; } - $schemaName = (string) $schemaConfig['schemaName']; - $compatibilityLevel = (string) $schemaConfig['compatibilityLevel']; + $schemaName = $schemaConfig['schemaName']; + $compatibilityLevel = $schemaConfig['compatibilityLevel']; $output->write( sprintf('Setting compatibility mode for schema "%s" to "%s"... ', $schemaName, $compatibilityLevel) @@ -114,7 +115,7 @@ public function execute(InputInterface $input, OutputInterface $output): int $this->outputSummary($output, $totalSchemas, $successCount, $failureCount); - return $failureCount > 0 ? 1 : 0; + return $failureCount > Command::SUCCESS ? Command::FAILURE : Command::SUCCESS; } /** @@ -152,21 +153,16 @@ private function setSchemaCompatibility( OutputInterface $output ): bool { try { - $result = $this->schemaRegistryApi->setSubjectCompatibilityLevel($schemaName, $compatibilityLevel); - - if (true === $result) { - $output->writeln('SUCCESS'); - return true; - } - - $output->writeln('FAILED'); - - return false; + $this->schemaRegistryApi->setSubjectCompatibilityLevel($schemaName, $compatibilityLevel); } catch (\Exception $e) { $output->writeln(sprintf('FAILED: %s', $e->getMessage())); return false; } + + $output->writeln('SUCCESS'); + + return true; } private function outputSummary(OutputInterface $output, int $total, int $success, int $failure): void diff --git a/src/Command/SetCompatibilityModeForSchemaCommand.php b/src/Command/SetCompatibilityModeForSchemaCommand.php index 2f65cc0..c95e35a 100644 --- a/src/Command/SetCompatibilityModeForSchemaCommand.php +++ b/src/Command/SetCompatibilityModeForSchemaCommand.php @@ -4,15 +4,13 @@ namespace Jobcloud\SchemaConsole\Command; +use Symfony\Component\Console\Command\Command; use Symfony\Component\Console\Input\InputArgument; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Output\OutputInterface; class SetCompatibilityModeForSchemaCommand extends AbstractSchemaCommand { - /** - * @return void - */ protected function configure(): void { $this @@ -28,16 +26,18 @@ public function execute(InputInterface $input, OutputInterface $output): int $schemaName = (string) $input->getArgument('schemaName'); $compatibilityLevel = (string) $input->getArgument('compatibilityLevel'); - $result = $this->schemaRegistryApi->setSubjectCompatibilityLevel($schemaName, $compatibilityLevel); + try { + $this->schemaRegistryApi->setSubjectCompatibilityLevel($schemaName, $compatibilityLevel); + } catch (\Exception $e) { + $output->writeln( + sprintf('Could not change compatibility mode for schema %s: %s', $schemaName, $e->getMessage()) + ); - if (true !== $result) { - $output->writeln(sprintf('Could not change compatibility mode for schema: %s', $schemaName)); - - return 1; + return Command::FAILURE; } $output->writeln(sprintf('Successfully changed compatibility mode for schema: %s', $schemaName)); - return 0; + return Command::SUCCESS; } } diff --git a/tests/Command/SetAllSchemasCompatibilityModeCommandTest.php b/tests/Command/SetAllSchemasCompatibilityModeCommandTest.php index 3270da7..0baa421 100644 --- a/tests/Command/SetAllSchemasCompatibilityModeCommandTest.php +++ b/tests/Command/SetAllSchemasCompatibilityModeCommandTest.php @@ -15,12 +15,22 @@ */ class SetAllSchemasCompatibilityModeCommandTest extends AbstractSchemaRegistryTestCase { + private const CONFIG_FILE = '/tmp/test_config.json'; + + protected function tearDown(): void + { + parent::tearDown(); + if (file_exists(self::CONFIG_FILE)) { + unlink(self::CONFIG_FILE); + } + } + public function testCommandWithValidConfigFileAndAllSuccessful(): void { - $configFile = $this->createTempConfigFile([ + file_put_contents(self::CONFIG_FILE, json_encode([ ['schemaName' => 'schema1', 'compatibilityLevel' => 'BACKWARD'], ['schemaName' => 'schema2', 'compatibilityLevel' => 'FORWARD'], - ]); + ])); /** @var MockObject|KafkaSchemaRegistryApiClient $schemaRegistryApi */ $schemaRegistryApi = $this->makeMock(KafkaSchemaRegistryApiClient::class, [ @@ -28,7 +38,7 @@ public function testCommandWithValidConfigFileAndAllSuccessful(): void ]); $commandTester = $this->createCommandTester($schemaRegistryApi); - $commandTester->execute(['configFile' => $configFile]); + $commandTester->execute(['configFile' => self::CONFIG_FILE]); $output = $commandTester->getDisplay(); @@ -45,25 +55,25 @@ public function testCommandWithValidConfigFileAndAllSuccessful(): void self::assertStringContainsString('Successful updates: 2', $output); self::assertStringContainsString('Failed updates: 0', $output); self::assertEquals(0, $commandTester->getStatusCode()); - - unlink($configFile); } public function testCommandWithValidConfigFileAndSomeFailures(): void { - $configFile = $this->createTempConfigFile([ + file_put_contents(self::CONFIG_FILE, json_encode([ ['schemaName' => 'schema1', 'compatibilityLevel' => 'BACKWARD'], ['schemaName' => 'schema2', 'compatibilityLevel' => 'FORWARD'], ['schemaName' => 'schema3', 'compatibilityLevel' => 'FULL'], - ]); + ])); /** @var MockObject|KafkaSchemaRegistryApiClient $schemaRegistryApi */ $schemaRegistryApi = $this->makeMock(KafkaSchemaRegistryApiClient::class); $schemaRegistryApi->method('setSubjectCompatibilityLevel') - ->willReturnOnConsecutiveCalls(true, false, true); + ->willReturnCallback(function ($schema) { + return $schema === 'schema2' ? throw new \Exception('error') : true; + }); $commandTester = $this->createCommandTester($schemaRegistryApi); - $commandTester->execute(['configFile' => $configFile]); + $commandTester->execute(['configFile' => self::CONFIG_FILE]); $output = $commandTester->getDisplay(); @@ -84,16 +94,14 @@ public function testCommandWithValidConfigFileAndSomeFailures(): void self::assertStringContainsString('Successful updates: 2', $output); self::assertStringContainsString('Failed updates: 1', $output); self::assertEquals(1, $commandTester->getStatusCode()); - - unlink($configFile); } public function testCommandWithApiException(): void { - $configFile = $this->createTempConfigFile([ + file_put_contents(self::CONFIG_FILE, json_encode([ ['schemaName' => 'schema1', 'compatibilityLevel' => 'BACKWARD'], ['schemaName' => 'schema2', 'compatibilityLevel' => 'FORWARD'], - ]); + ])); /** @var MockObject|KafkaSchemaRegistryApiClient $schemaRegistryApi */ $schemaRegistryApi = $this->makeMock(KafkaSchemaRegistryApiClient::class); @@ -104,7 +112,7 @@ public function testCommandWithApiException(): void ); $commandTester = $this->createCommandTester($schemaRegistryApi); - $commandTester->execute(['configFile' => $configFile]); + $commandTester->execute(['configFile' => self::CONFIG_FILE]); $output = $commandTester->getDisplay(); @@ -119,8 +127,6 @@ public function testCommandWithApiException(): void self::assertStringContainsString('Successful updates: 1', $output); self::assertStringContainsString('Failed updates: 1', $output); self::assertEquals(1, $commandTester->getStatusCode()); - - unlink($configFile); } public function testCommandWithNonExistentConfigFile(): void @@ -142,36 +148,13 @@ public function testCommandWithNonExistentConfigFile(): void */ public function testCommandWithInvalidJsonConfiguration(mixed $configData): void { - if (is_string($configData)) { - $configFile = tempnam(sys_get_temp_dir(), 'test_config'); - file_put_contents($configFile, $configData); - - /** @var MockObject|KafkaSchemaRegistryApiClient $schemaRegistryApi */ - $schemaRegistryApi = $this->makeMock(KafkaSchemaRegistryApiClient::class); - - $commandTester = $this->createCommandTester($schemaRegistryApi); - $commandTester->execute(['configFile' => $configFile]); - - $output = $commandTester->getDisplay(); - - self::assertStringContainsString( - 'Configuration file must contain a JSON array of schema configurations', - $output - ); - self::assertEquals(1, $commandTester->getStatusCode()); - - unlink($configFile); - - return; - } - - $configFile = $this->createTempConfigFile($configData); + file_put_contents(self::CONFIG_FILE, json_encode($configData)); /** @var MockObject|KafkaSchemaRegistryApiClient $schemaRegistryApi */ $schemaRegistryApi = $this->makeMock(KafkaSchemaRegistryApiClient::class); $commandTester = $this->createCommandTester($schemaRegistryApi); - $commandTester->execute(['configFile' => $configFile]); + $commandTester->execute(['configFile' => self::CONFIG_FILE]); $output = $commandTester->getDisplay(); @@ -180,8 +163,6 @@ public function testCommandWithInvalidJsonConfiguration(mixed $configData): void $output ); self::assertEquals(1, $commandTester->getStatusCode()); - - unlink($configFile); } /** @@ -202,10 +183,10 @@ public static function invalidJsonConfigurationProvider(): array public function testCommandWithMissingSchemaNameField(): void { - $configFile = $this->createTempConfigFile([ + file_put_contents(self::CONFIG_FILE, json_encode([ ['compatibilityLevel' => 'BACKWARD'], ['schemaName' => 'valid-schema', 'compatibilityLevel' => 'FORWARD'], - ]); + ])); /** @var MockObject|KafkaSchemaRegistryApiClient $schemaRegistryApi */ $schemaRegistryApi = $this->makeMock(KafkaSchemaRegistryApiClient::class, [ @@ -213,7 +194,7 @@ public function testCommandWithMissingSchemaNameField(): void ]); $commandTester = $this->createCommandTester($schemaRegistryApi); - $commandTester->execute(['configFile' => $configFile]); + $commandTester->execute(['configFile' => self::CONFIG_FILE]); $output = $commandTester->getDisplay(); @@ -228,16 +209,14 @@ public function testCommandWithMissingSchemaNameField(): void self::assertStringContainsString('Successful updates: 1', $output); self::assertStringContainsString('Failed updates: 1', $output); self::assertEquals(1, $commandTester->getStatusCode()); - - unlink($configFile); } public function testCommandWithMissingCompatibilityLevelField(): void { - $configFile = $this->createTempConfigFile([ + file_put_contents(self::CONFIG_FILE, json_encode([ ['schemaName' => 'incomplete-schema'], ['schemaName' => 'valid-schema', 'compatibilityLevel' => 'BACKWARD'], - ]); + ])); /** @var MockObject|KafkaSchemaRegistryApiClient $schemaRegistryApi */ $schemaRegistryApi = $this->makeMock(KafkaSchemaRegistryApiClient::class, [ @@ -245,7 +224,7 @@ public function testCommandWithMissingCompatibilityLevelField(): void ]); $commandTester = $this->createCommandTester($schemaRegistryApi); - $commandTester->execute(['configFile' => $configFile]); + $commandTester->execute(['configFile' => self::CONFIG_FILE]); $output = $commandTester->getDisplay(); @@ -260,19 +239,17 @@ public function testCommandWithMissingCompatibilityLevelField(): void self::assertStringContainsString('Successful updates: 1', $output); self::assertStringContainsString('Failed updates: 1', $output); self::assertEquals(1, $commandTester->getStatusCode()); - - unlink($configFile); } public function testCommandWithEmptyArray(): void { - $configFile = $this->createTempConfigFile([]); + file_put_contents(self::CONFIG_FILE, json_encode([])); /** @var MockObject|KafkaSchemaRegistryApiClient $schemaRegistryApi */ $schemaRegistryApi = $this->makeMock(KafkaSchemaRegistryApiClient::class); $commandTester = $this->createCommandTester($schemaRegistryApi); - $commandTester->execute(['configFile' => $configFile]); + $commandTester->execute(['configFile' => self::CONFIG_FILE]); $output = $commandTester->getDisplay(); @@ -281,8 +258,6 @@ public function testCommandWithEmptyArray(): void self::assertStringContainsString('Successful updates: 0', $output); self::assertStringContainsString('Failed updates: 0', $output); self::assertEquals(0, $commandTester->getStatusCode()); - - unlink($configFile); } public function testCommandWithAllValidCompatibilityLevels(): void @@ -297,7 +272,7 @@ public function testCommandWithAllValidCompatibilityLevels(): void $schemas[] = ['schemaName' => "schema-{$level}", 'compatibilityLevel' => $level]; } - $configFile = $this->createTempConfigFile($schemas); + file_put_contents(self::CONFIG_FILE, json_encode($schemas)); /** @var MockObject|KafkaSchemaRegistryApiClient $schemaRegistryApi */ $schemaRegistryApi = $this->makeMock(KafkaSchemaRegistryApiClient::class, [ @@ -305,7 +280,7 @@ public function testCommandWithAllValidCompatibilityLevels(): void ]); $commandTester = $this->createCommandTester($schemaRegistryApi); - $commandTester->execute(['configFile' => $configFile]); + $commandTester->execute(['configFile' => self::CONFIG_FILE]); $output = $commandTester->getDisplay(); @@ -321,8 +296,6 @@ public function testCommandWithAllValidCompatibilityLevels(): void self::assertStringContainsString('Successful updates: 7', $output); self::assertStringContainsString('Failed updates: 0', $output); self::assertEquals(0, $commandTester->getStatusCode()); - - unlink($configFile); } private function createCommandTester(MockObject $schemaRegistryApi): CommandTester @@ -333,12 +306,4 @@ private function createCommandTester(MockObject $schemaRegistryApi): CommandTest return new CommandTester($command); } - - private function createTempConfigFile(mixed $config): string - { - $configFile = tempnam(sys_get_temp_dir(), 'test_config'); - file_put_contents($configFile, json_encode($config)); - - return $configFile; - } } diff --git a/tests/Command/SetCompatibilityModeForSchemaCommandTest.php b/tests/Command/SetCompatibilityModeForSchemaCommandTest.php index 9c04671..04b1de7 100644 --- a/tests/Command/SetCompatibilityModeForSchemaCommandTest.php +++ b/tests/Command/SetCompatibilityModeForSchemaCommandTest.php @@ -47,10 +47,11 @@ public function testCommandWhenCompatibilityIsChanged(): void public function testCommandWhenCompatibilityIsNotChanged(): void { $schemaName = 'SomeSchemaName'; + $errorMessage = 'error'; /** @var MockObject|KafkaSchemaRegistryApiClient $schemaRegistryApi */ $schemaRegistryApi = $this->makeMock(KafkaSchemaRegistryApiClient::class, [ - 'setSubjectCompatibilityLevel' => false, + 'setSubjectCompatibilityLevel' => new \Exception($errorMessage), ]); $application = new Application(); @@ -66,7 +67,7 @@ public function testCommandWhenCompatibilityIsNotChanged(): void $commandOutput = trim($commandTester->getDisplay()); self::assertEquals( - sprintf('Could not change compatibility mode for schema: %s', $schemaName), + sprintf('Could not change compatibility mode for schema %s: %s', $schemaName, $errorMessage), $commandOutput ); self::assertEquals(1, $commandTester->getStatusCode()); From ed3ba39e8ebf418a8cdb60c6ce816b46c75c87b6 Mon Sep 17 00:00:00 2001 From: Marko Date: Thu, 9 Oct 2025 14:40:34 +0200 Subject: [PATCH 9/9] Fix condition --- src/Command/SetAllSchemasCompatibilityModeCommand.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Command/SetAllSchemasCompatibilityModeCommand.php b/src/Command/SetAllSchemasCompatibilityModeCommand.php index 266e81e..f04d99f 100644 --- a/src/Command/SetAllSchemasCompatibilityModeCommand.php +++ b/src/Command/SetAllSchemasCompatibilityModeCommand.php @@ -115,7 +115,7 @@ public function execute(InputInterface $input, OutputInterface $output): int $this->outputSummary($output, $totalSchemas, $successCount, $failureCount); - return $failureCount > Command::SUCCESS ? Command::FAILURE : Command::SUCCESS; + return $failureCount > 0 ? Command::FAILURE : Command::SUCCESS; } /**