diff --git a/src/Symfony/Component/Notifier/Bridge/MicrosoftTeams/Action/ActionCard.php b/src/Symfony/Component/Notifier/Bridge/MicrosoftTeams/Action/ActionCard.php new file mode 100644 index 0000000000000..f036ea98bc3f3 --- /dev/null +++ b/src/Symfony/Component/Notifier/Bridge/MicrosoftTeams/Action/ActionCard.php @@ -0,0 +1,51 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Notifier\Bridge\MicrosoftTeams\Action; + +use Symfony\Component\Notifier\Bridge\MicrosoftTeams\Action\Input\InputInterface; + +/** + * @author Edouard Lescot + * @author Oskar Stark + * + * @see https://docs.microsoft.com/en-us/outlook/actionable-messages/message-card-reference#actioncard-action + */ +final class ActionCard implements ActionInterface +{ + private $options = []; + + public function name(string $name): self + { + $this->options['name'] = $name; + + return $this; + } + + public function input(InputInterface $inputAction): self + { + $this->options['inputs'][] = $inputAction->toArray(); + + return $this; + } + + public function action(ActionCardCompatibleActionInterface $action): self + { + $this->options['actions'][] = $action->toArray(); + + return $this; + } + + public function toArray(): array + { + return $this->options + ['@type' => 'ActionCard']; + } +} diff --git a/src/Symfony/Component/Notifier/Bridge/MicrosoftTeams/Action/ActionCardCompatibleActionInterface.php b/src/Symfony/Component/Notifier/Bridge/MicrosoftTeams/Action/ActionCardCompatibleActionInterface.php new file mode 100644 index 0000000000000..85d9c52545d95 --- /dev/null +++ b/src/Symfony/Component/Notifier/Bridge/MicrosoftTeams/Action/ActionCardCompatibleActionInterface.php @@ -0,0 +1,21 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Notifier\Bridge\MicrosoftTeams\Action; + +/** + * An Action which can be used inside an ActionCard. + * + * @author Oskar Stark + */ +interface ActionCardCompatibleActionInterface extends ActionInterface +{ +} diff --git a/src/Symfony/Component/Notifier/Bridge/MicrosoftTeams/Action/ActionInterface.php b/src/Symfony/Component/Notifier/Bridge/MicrosoftTeams/Action/ActionInterface.php new file mode 100644 index 0000000000000..d37b13b9faf07 --- /dev/null +++ b/src/Symfony/Component/Notifier/Bridge/MicrosoftTeams/Action/ActionInterface.php @@ -0,0 +1,21 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Notifier\Bridge\MicrosoftTeams\Action; + +/** + * @author Edouard Lescot + * @author Oskar Stark + */ +interface ActionInterface +{ + public function toArray(): array; +} diff --git a/src/Symfony/Component/Notifier/Bridge/MicrosoftTeams/Action/Element/Header.php b/src/Symfony/Component/Notifier/Bridge/MicrosoftTeams/Action/Element/Header.php new file mode 100644 index 0000000000000..cc636a44cb1f3 --- /dev/null +++ b/src/Symfony/Component/Notifier/Bridge/MicrosoftTeams/Action/Element/Header.php @@ -0,0 +1,42 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Notifier\Bridge\MicrosoftTeams\Action\Element; + +/** + * @author Edouard Lescot + * @author Oskar Stark + * + * @see https://docs.microsoft.com/en-us/outlook/actionable-messages/message-card-reference#header + */ +final class Header +{ + private $options = []; + + public function name(string $name): self + { + $this->options['name'] = $name; + + return $this; + } + + public function value(string $value): self + { + $this->options['value'] = $value; + + return $this; + } + + public function toArray(): array + { + return $this->options; + } +} diff --git a/src/Symfony/Component/Notifier/Bridge/MicrosoftTeams/Action/HttpPostAction.php b/src/Symfony/Component/Notifier/Bridge/MicrosoftTeams/Action/HttpPostAction.php new file mode 100644 index 0000000000000..99b943c86b7a8 --- /dev/null +++ b/src/Symfony/Component/Notifier/Bridge/MicrosoftTeams/Action/HttpPostAction.php @@ -0,0 +1,65 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Notifier\Bridge\MicrosoftTeams\Action; + +use Symfony\Component\Notifier\Bridge\MicrosoftTeams\Action\Element\Header; + +/** + * @author Edouard Lescot + * @author Oskar Stark + * + * @see https://docs.microsoft.com/en-us/outlook/actionable-messages/message-card-reference#httppost-action + */ +final class HttpPostAction implements ActionCardCompatibleActionInterface +{ + private $options = ['@type' => 'HttpPOST']; + + public function name(string $name): self + { + $this->options['name'] = $name; + + return $this; + } + + public function target(string $url): self + { + $this->options['target'] = $url; + + return $this; + } + + public function header(Header $header): self + { + $this->options['headers'][] = $header->toArray(); + + return $this; + } + + public function body(string $body): self + { + $this->options['body'] = $body; + + return $this; + } + + public function bodyContentType(string $contentType): self + { + $this->options['bodyContentType'] = $contentType; + + return $this; + } + + public function toArray(): array + { + return $this->options; + } +} diff --git a/src/Symfony/Component/Notifier/Bridge/MicrosoftTeams/Action/Input/AbstractInput.php b/src/Symfony/Component/Notifier/Bridge/MicrosoftTeams/Action/Input/AbstractInput.php new file mode 100644 index 0000000000000..9c5f22a39929e --- /dev/null +++ b/src/Symfony/Component/Notifier/Bridge/MicrosoftTeams/Action/Input/AbstractInput.php @@ -0,0 +1,54 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Notifier\Bridge\MicrosoftTeams\Action\Input; + +/** + * @author Edouard Lescot + * @author Oskar Stark + */ +abstract class AbstractInput implements InputInterface +{ + private $options = []; + + public function id(string $id): self + { + $this->options['id'] = $id; + + return $this; + } + + public function isRequired(bool $required): self + { + $this->options['isRequired'] = $required; + + return $this; + } + + public function title(string $title): self + { + $this->options['title'] = $title; + + return $this; + } + + public function value(string $value): self + { + $this->options['value'] = $value; + + return $this; + } + + public function toArray(): array + { + return $this->options; + } +} diff --git a/src/Symfony/Component/Notifier/Bridge/MicrosoftTeams/Action/Input/DateInput.php b/src/Symfony/Component/Notifier/Bridge/MicrosoftTeams/Action/Input/DateInput.php new file mode 100644 index 0000000000000..1794dad3e131b --- /dev/null +++ b/src/Symfony/Component/Notifier/Bridge/MicrosoftTeams/Action/Input/DateInput.php @@ -0,0 +1,35 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Notifier\Bridge\MicrosoftTeams\Action\Input; + +/** + * @author Edouard Lescot + * @author Oskar Stark + * + * @see https://docs.microsoft.com/en-us/outlook/actionable-messages/message-card-reference#dateinput + */ +final class DateInput extends AbstractInput +{ + private $options = []; + + public function includeTime(bool $includeTime): self + { + $this->options['includeTime'] = $includeTime; + + return $this; + } + + public function toArray(): array + { + return parent::toArray() + $this->options + ['@type' => 'DateInput']; + } +} diff --git a/src/Symfony/Component/Notifier/Bridge/MicrosoftTeams/Action/Input/InputInterface.php b/src/Symfony/Component/Notifier/Bridge/MicrosoftTeams/Action/Input/InputInterface.php new file mode 100644 index 0000000000000..8a1c4761346fd --- /dev/null +++ b/src/Symfony/Component/Notifier/Bridge/MicrosoftTeams/Action/Input/InputInterface.php @@ -0,0 +1,21 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Notifier\Bridge\MicrosoftTeams\Action\Input; + +/** + * @author Edouard Lescot + * @author Oskar Stark + */ +interface InputInterface +{ + public function toArray(): array; +} diff --git a/src/Symfony/Component/Notifier/Bridge/MicrosoftTeams/Action/Input/MultiChoiceInput.php b/src/Symfony/Component/Notifier/Bridge/MicrosoftTeams/Action/Input/MultiChoiceInput.php new file mode 100644 index 0000000000000..85403f6378adc --- /dev/null +++ b/src/Symfony/Component/Notifier/Bridge/MicrosoftTeams/Action/Input/MultiChoiceInput.php @@ -0,0 +1,60 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Notifier\Bridge\MicrosoftTeams\Action\Input; + +use Symfony\Component\Notifier\Exception\InvalidArgumentException; + +/** + * @author Edouard Lescot + * @author Oskar Stark + * + * @see https://docs.microsoft.com/en-us/outlook/actionable-messages/message-card-reference#multichoiceinput + */ +final class MultiChoiceInput extends AbstractInput +{ + private const STYLES = [ + 'expanded', + 'normal', + ]; + + private $options = []; + + public function choice(string $display, string $value): self + { + $this->options['choices'][] = ['display' => $display, 'value' => $value]; + + return $this; + } + + public function isMultiSelect(bool $multiSelect): self + { + $this->options['isMultiSelect'] = $multiSelect; + + return $this; + } + + public function style(string $style): self + { + if (!\in_array($style, self::STYLES)) { + throw new InvalidArgumentException(sprintf('Supported styles for "%s" method are: "%s".', __METHOD__, implode('", "', self::STYLES))); + } + + $this->options['style'] = $style; + + return $this; + } + + public function toArray(): array + { + return parent::toArray() + $this->options + ['@type' => 'MultichoiceInput']; + } +} diff --git a/src/Symfony/Component/Notifier/Bridge/MicrosoftTeams/Action/Input/TextInput.php b/src/Symfony/Component/Notifier/Bridge/MicrosoftTeams/Action/Input/TextInput.php new file mode 100644 index 0000000000000..a62edddcd58b1 --- /dev/null +++ b/src/Symfony/Component/Notifier/Bridge/MicrosoftTeams/Action/Input/TextInput.php @@ -0,0 +1,42 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Notifier\Bridge\MicrosoftTeams\Action\Input; + +/** + * @author Edouard Lescot + * @author Oskar Stark + * + * @see https://docs.microsoft.com/en-us/outlook/actionable-messages/message-card-reference#textinput + */ +final class TextInput extends AbstractInput +{ + private $options = []; + + public function isMultiline(bool $multiline): self + { + $this->options['isMultiline'] = $multiline; + + return $this; + } + + public function maxLength(int $maxLength): self + { + $this->options['maxLength'] = $maxLength; + + return $this; + } + + public function toArray(): array + { + return parent::toArray() + $this->options + ['@type' => 'TextInput']; + } +} diff --git a/src/Symfony/Component/Notifier/Bridge/MicrosoftTeams/Action/InvokeAddInCommandAction.php b/src/Symfony/Component/Notifier/Bridge/MicrosoftTeams/Action/InvokeAddInCommandAction.php new file mode 100644 index 0000000000000..e305f4fd84526 --- /dev/null +++ b/src/Symfony/Component/Notifier/Bridge/MicrosoftTeams/Action/InvokeAddInCommandAction.php @@ -0,0 +1,56 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Notifier\Bridge\MicrosoftTeams\Action; + +/** + * @author Edouard Lescot + * @author Oskar Stark + * + * @see https://docs.microsoft.com/en-us/outlook/actionable-messages/message-card-reference#invokeaddincommand-action + */ +final class InvokeAddInCommandAction implements ActionInterface +{ + private $options = []; + + public function name(string $name): self + { + $this->options['name'] = $name; + + return $this; + } + + public function addInId(string $addInId): self + { + $this->options['addInId'] = $addInId; + + return $this; + } + + public function desktopCommandId(string $desktopCommandId): self + { + $this->options['desktopCommandId'] = $desktopCommandId; + + return $this; + } + + public function initializationContext(array $context): self + { + $this->options['initializationContext'] = $context; + + return $this; + } + + public function toArray(): array + { + return $this->options + ['@type' => 'InvokeAddInCommand']; + } +} diff --git a/src/Symfony/Component/Notifier/Bridge/MicrosoftTeams/Action/OpenUriAction.php b/src/Symfony/Component/Notifier/Bridge/MicrosoftTeams/Action/OpenUriAction.php new file mode 100644 index 0000000000000..39c28054da552 --- /dev/null +++ b/src/Symfony/Component/Notifier/Bridge/MicrosoftTeams/Action/OpenUriAction.php @@ -0,0 +1,55 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Notifier\Bridge\MicrosoftTeams\Action; + +use Symfony\Component\Notifier\Exception\InvalidArgumentException; + +/** + * @author Edouard Lescot + * @author Oskar Stark + * + * @see https://docs.microsoft.com/en-us/outlook/actionable-messages/message-card-reference#openuri-action + */ +final class OpenUriAction implements ActionCardCompatibleActionInterface +{ + private const OPERATING_SYSTEMS = [ + 'android', + 'default', + 'iOS', + 'windows', + ]; + + private $options = []; + + public function name(string $name): self + { + $this->options['name'] = $name; + + return $this; + } + + public function target(string $uri, string $os = 'default'): self + { + if (!\in_array($os, self::OPERATING_SYSTEMS)) { + throw new InvalidArgumentException(sprintf('Supported operating systems for "%s" method are: "%s".', __METHOD__, implode('", "', self::OPERATING_SYSTEMS))); + } + + $this->options['targets'][] = ['os' => $os, 'uri' => $uri]; + + return $this; + } + + public function toArray(): array + { + return $this->options + ['@type' => 'OpenUri']; + } +} diff --git a/src/Symfony/Component/Notifier/Bridge/MicrosoftTeams/CHANGELOG.md b/src/Symfony/Component/Notifier/Bridge/MicrosoftTeams/CHANGELOG.md index 1f2b652ac20ea..f24fc06e6faf3 100644 --- a/src/Symfony/Component/Notifier/Bridge/MicrosoftTeams/CHANGELOG.md +++ b/src/Symfony/Component/Notifier/Bridge/MicrosoftTeams/CHANGELOG.md @@ -5,3 +5,4 @@ CHANGELOG --- * Add the bridge + * Add options support diff --git a/src/Symfony/Component/Notifier/Bridge/MicrosoftTeams/MicrosoftTeamsOptions.php b/src/Symfony/Component/Notifier/Bridge/MicrosoftTeams/MicrosoftTeamsOptions.php new file mode 100644 index 0000000000000..bd3d01393f17e --- /dev/null +++ b/src/Symfony/Component/Notifier/Bridge/MicrosoftTeams/MicrosoftTeamsOptions.php @@ -0,0 +1,160 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Notifier\Bridge\MicrosoftTeams; + +use Symfony\Component\Notifier\Bridge\MicrosoftTeams\Action\ActionInterface; +use Symfony\Component\Notifier\Bridge\MicrosoftTeams\Section\Section; +use Symfony\Component\Notifier\Bridge\MicrosoftTeams\Section\SectionInterface; +use Symfony\Component\Notifier\Exception\InvalidArgumentException; +use Symfony\Component\Notifier\Message\MessageOptionsInterface; +use Symfony\Component\Notifier\Notification\Notification; + +/** + * @author Edouard Lescot + * @author Oskar Stark + * + * @see https://docs.microsoft.com/en-us/outlook/actionable-messages/message-card-reference + */ +final class MicrosoftTeamsOptions implements MessageOptionsInterface +{ + private const MAX_ACTIONS = 4; + + private $options = []; + + public function __construct(array $options = []) + { + if (\array_key_exists('themeColor', $options)) { + $this->validateThemeColor($options['themeColor']); + } + + $this->options = $options; + + $this->validateNumberOfActions(); + } + + public static function fromNotification(Notification $notification): self + { + $options = (new self()) + ->title($notification->getSubject()) + ->text($notification->getContent()); + + if ($exception = $notification->getExceptionAsString()) { + $options->section((new Section())->text($exception)); + } + + return $options; + } + + public function toArray(): array + { + $options = $this->options; + + // Send a text, not a message card + if (1 === \count($options) && isset($options['text'])) { + return $options; + } + + $options['@type'] = 'MessageCard'; + $options['@context'] = 'https://schema.org/extensions'; + + return $options; + } + + public function getRecipientId(): ?string + { + return $this->options['recipient_id'] ?? null; + } + + /** + * @param string $path The hook path (anything after https://outlook.office.com) + */ + public function recipient(string $path): self + { + if (!preg_match('/^\/webhook\//', $path)) { + throw new InvalidArgumentException(sprintf('"%s" require recipient id format to be "/webhook/{uuid}@{uuid}/IncomingWebhook/{id}/{uuid}", "%s" given.', __CLASS__, $path)); + } + + $this->options['recipient_id'] = $path; + + return $this; + } + + /** + * @param string $summary Markdown string + */ + public function summary(string $summary): self + { + $this->options['summary'] = $summary; + + return $this; + } + + public function title(string $title): self + { + $this->options['title'] = $title; + + return $this; + } + + public function text(string $text): self + { + $this->options['text'] = $text; + + return $this; + } + + public function themeColor(string $themeColor): self + { + $this->validateThemeColor($themeColor); + + $this->options['themeColor'] = $themeColor; + + return $this; + } + + public function section(SectionInterface $section): self + { + $this->options['sections'][] = $section->toArray(); + + return $this; + } + + public function action(ActionInterface $action): self + { + $this->validateNumberOfActions(); + + $this->options['potentialAction'][] = $action->toArray(); + + return $this; + } + + public function expectedActor(string $actor): self + { + $this->options['expectedActors'][] = $actor; + + return $this; + } + + private function validateNumberOfActions(): void + { + if (\count($this->options['potentialAction'] ?? []) >= self::MAX_ACTIONS) { + throw new InvalidArgumentException(sprintf('MessageCard maximum number of "potentialAction" has been reached (%d).', self::MAX_ACTIONS)); + } + } + + private function validateThemeColor(string $themeColor): void + { + if (!preg_match('/^#([0-9a-f]{6}|[0-9a-f]{3})$/i', $themeColor)) { + throw new InvalidArgumentException('MessageCard themeColor must have a valid hex color format.'); + } + } +} diff --git a/src/Symfony/Component/Notifier/Bridge/MicrosoftTeams/MicrosoftTeamsTransport.php b/src/Symfony/Component/Notifier/Bridge/MicrosoftTeams/MicrosoftTeamsTransport.php index ccd0f4cdf770a..6f7322c758a46 100644 --- a/src/Symfony/Component/Notifier/Bridge/MicrosoftTeams/MicrosoftTeamsTransport.php +++ b/src/Symfony/Component/Notifier/Bridge/MicrosoftTeams/MicrosoftTeamsTransport.php @@ -11,6 +11,7 @@ namespace Symfony\Component\Notifier\Bridge\MicrosoftTeams; +use Symfony\Component\Notifier\Exception\LogicException; use Symfony\Component\Notifier\Exception\TransportException; use Symfony\Component\Notifier\Exception\UnsupportedMessageTypeException; use Symfony\Component\Notifier\Message\ChatMessage; @@ -56,12 +57,18 @@ protected function doSend(MessageInterface $message): SentMessage throw new UnsupportedMessageTypeException(__CLASS__, ChatMessage::class, $message); } + if ($message->getOptions() && !$message->getOptions() instanceof MicrosoftTeamsOptions) { + throw new LogicException(sprintf('The "%s" transport only supports instances of "%s" for options.', __CLASS__, MicrosoftTeamsOptions::class)); + } + + $options = ($opts = $message->getOptions()) ? $opts->toArray() : []; + + $options['text'] = $options['text'] ?? $message->getSubject(); + $path = $message->getRecipientId() ?? $this->path; $endpoint = sprintf('https://%s%s', $this->getEndpoint(), $path); $response = $this->client->request('POST', $endpoint, [ - 'json' => [ - 'text' => $message->getSubject(), - ], + 'json' => $options, ]); $requestId = $response->getHeaders(false)['request-id'][0] ?? null; diff --git a/src/Symfony/Component/Notifier/Bridge/MicrosoftTeams/Section/Field/Activity.php b/src/Symfony/Component/Notifier/Bridge/MicrosoftTeams/Section/Field/Activity.php new file mode 100644 index 0000000000000..33e0bdeb11725 --- /dev/null +++ b/src/Symfony/Component/Notifier/Bridge/MicrosoftTeams/Section/Field/Activity.php @@ -0,0 +1,56 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Notifier\Bridge\MicrosoftTeams\Section\Field; + +/** + * @author Edouard Lescot + * @author Oskar Stark + * + * @see https://docs.microsoft.com/en-us/outlook/actionable-messages/message-card-reference#section-fields + */ +final class Activity +{ + private $options = []; + + public function image(string $imageUrl): self + { + $this->options['activityImage'] = $imageUrl; + + return $this; + } + + public function title(string $title): self + { + $this->options['activityTitle'] = $title; + + return $this; + } + + public function subtitle(string $subtitle): self + { + $this->options['activitySubtitle'] = $subtitle; + + return $this; + } + + public function text(string $text): self + { + $this->options['activityText'] = $text; + + return $this; + } + + public function toArray(): array + { + return $this->options; + } +} diff --git a/src/Symfony/Component/Notifier/Bridge/MicrosoftTeams/Section/Field/Fact.php b/src/Symfony/Component/Notifier/Bridge/MicrosoftTeams/Section/Field/Fact.php new file mode 100644 index 0000000000000..37fa4bb627140 --- /dev/null +++ b/src/Symfony/Component/Notifier/Bridge/MicrosoftTeams/Section/Field/Fact.php @@ -0,0 +1,39 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Notifier\Bridge\MicrosoftTeams\Section\Field; + +/** + * @author Oskar Stark + */ +final class Fact +{ + private $options = []; + + public function name(string $name): self + { + $this->options['name'] = $name; + + return $this; + } + + public function value(string $value): self + { + $this->options['value'] = $value; + + return $this; + } + + public function toArray(): array + { + return $this->options; + } +} diff --git a/src/Symfony/Component/Notifier/Bridge/MicrosoftTeams/Section/Field/Image.php b/src/Symfony/Component/Notifier/Bridge/MicrosoftTeams/Section/Field/Image.php new file mode 100644 index 0000000000000..49f65bae9d8f0 --- /dev/null +++ b/src/Symfony/Component/Notifier/Bridge/MicrosoftTeams/Section/Field/Image.php @@ -0,0 +1,42 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Notifier\Bridge\MicrosoftTeams\Section\Field; + +/** + * @author Edouard Lescot + * @author Oskar Stark + * + * @see https://docs.microsoft.com/en-us/outlook/actionable-messages/message-card-reference#image-object + */ +final class Image +{ + private $options = []; + + public function image(string $imageUrl): self + { + $this->options['image'] = $imageUrl; + + return $this; + } + + public function title(string $title): self + { + $this->options['title'] = $title; + + return $this; + } + + public function toArray(): array + { + return $this->options; + } +} diff --git a/src/Symfony/Component/Notifier/Bridge/MicrosoftTeams/Section/Section.php b/src/Symfony/Component/Notifier/Bridge/MicrosoftTeams/Section/Section.php new file mode 100644 index 0000000000000..eb0856f05bf97 --- /dev/null +++ b/src/Symfony/Component/Notifier/Bridge/MicrosoftTeams/Section/Section.php @@ -0,0 +1,84 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Notifier\Bridge\MicrosoftTeams\Section; + +use Symfony\Component\Notifier\Bridge\MicrosoftTeams\Action\ActionInterface; +use Symfony\Component\Notifier\Bridge\MicrosoftTeams\Section\Field\Activity; +use Symfony\Component\Notifier\Bridge\MicrosoftTeams\Section\Field\Fact; +use Symfony\Component\Notifier\Bridge\MicrosoftTeams\Section\Field\Image; + +/** + * @author Edouard Lescot + * @author Oskar Stark + * + * @see https://docs.microsoft.com/en-us/outlook/actionable-messages/message-card-reference#section-fields + */ +final class Section implements SectionInterface +{ + private $options = []; + + public function title(string $title): self + { + $this->options['title'] = $title; + + return $this; + } + + public function text(string $text): self + { + $this->options['text'] = $text; + + return $this; + } + + public function action(ActionInterface $action): self + { + $this->options['potentialAction'][] = $action->toArray(); + + return $this; + } + + public function activity(Activity $activity): self + { + foreach ($activity->toArray() as $key => $element) { + $this->options[$key] = $element; + } + + return $this; + } + + public function image(Image $image): self + { + $this->options['images'][] = $image->toArray(); + + return $this; + } + + public function fact(Fact $fact): self + { + $this->options['facts'][] = $fact->toArray(); + + return $this; + } + + public function markdown(bool $markdown): self + { + $this->options['markdown'] = $markdown; + + return $this; + } + + public function toArray(): array + { + return $this->options; + } +} diff --git a/src/Symfony/Component/Notifier/Bridge/MicrosoftTeams/Section/SectionInterface.php b/src/Symfony/Component/Notifier/Bridge/MicrosoftTeams/Section/SectionInterface.php new file mode 100644 index 0000000000000..0ca5e22e75ff9 --- /dev/null +++ b/src/Symfony/Component/Notifier/Bridge/MicrosoftTeams/Section/SectionInterface.php @@ -0,0 +1,21 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Notifier\Bridge\MicrosoftTeams\Section; + +/** + * @author Edouard Lescot + * @author Oskar Stark + */ +interface SectionInterface +{ + public function toArray(): array; +} diff --git a/src/Symfony/Component/Notifier/Bridge/MicrosoftTeams/Test/Action/Input/AbstractInputTestCase.php b/src/Symfony/Component/Notifier/Bridge/MicrosoftTeams/Test/Action/Input/AbstractInputTestCase.php new file mode 100644 index 0000000000000..fc26c37bebbd6 --- /dev/null +++ b/src/Symfony/Component/Notifier/Bridge/MicrosoftTeams/Test/Action/Input/AbstractInputTestCase.php @@ -0,0 +1,68 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Notifier\Bridge\MicrosoftTeams\Test\Action\Input; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\Notifier\Bridge\MicrosoftTeams\Action\Input\AbstractInput; + +/** + * @author Oskar Stark + */ +abstract class AbstractInputTestCase extends TestCase +{ + abstract public function createInput(): AbstractInput; + + public function testId() + { + $input = $this->createInput(); + + $input->id($value = '1234'); + + $this->assertSame($value, $input->toArray()['id']); + } + + public function testIsRequiredWithFalse() + { + $input = $this->createInput(); + + $input->isRequired(false); + + $this->assertFalse($input->toArray()['isRequired']); + } + + public function testIsRequiredWithTrue() + { + $input = $this->createInput(); + + $input->isRequired(true); + + $this->assertTrue($input->toArray()['isRequired']); + } + + public function testTitle() + { + $input = $this->createInput(); + + $input->title($value = 'Hey Symfony!'); + + $this->assertSame($value, $input->toArray()['title']); + } + + public function testValue() + { + $input = $this->createInput(); + + $input->value($value = 'Community power!'); + + $this->assertSame($value, $input->toArray()['value']); + } +} diff --git a/src/Symfony/Component/Notifier/Bridge/MicrosoftTeams/Tests/Action/ActionCardTest.php b/src/Symfony/Component/Notifier/Bridge/MicrosoftTeams/Tests/Action/ActionCardTest.php new file mode 100644 index 0000000000000..27496f3266fa2 --- /dev/null +++ b/src/Symfony/Component/Notifier/Bridge/MicrosoftTeams/Tests/Action/ActionCardTest.php @@ -0,0 +1,71 @@ +name($value = 'My name'); + + $this->assertSame($value, $action->toArray()['name']); + } + + /** + * @dataProvider availableInputs + */ + public function testInput(array $expected, InputInterface $input) + { + $action = (new ActionCard()) + ->input($input); + + $this->assertCount(1, $action->toArray()['inputs']); + $this->assertSame($expected, $action->toArray()['inputs']); + } + + public function availableInputs(): \Generator + { + yield [[['@type' => 'DateInput']], new DateInput()]; + yield [[['@type' => 'TextInput']], new TextInput()]; + yield [[['@type' => 'MultichoiceInput']], new MultiChoiceInput()]; + } + + /** + * @dataProvider compatibleActions + */ + public function testAction(array $expected, ActionCardCompatibleActionInterface $action) + { + $section = (new ActionCard()) + ->action($action); + + $this->assertCount(1, $section->toArray()['actions']); + $this->assertSame($expected, $section->toArray()['actions']); + } + + public function compatibleActions(): \Generator + { + yield [[['@type' => 'HttpPOST']], new HttpPostAction()]; + yield [[['@type' => 'OpenUri']], new OpenUriAction()]; + } + + public function testToArray() + { + $this->assertSame( + [ + '@type' => 'ActionCard', + ], + (new ActionCard())->toArray() + ); + } +} diff --git a/src/Symfony/Component/Notifier/Bridge/MicrosoftTeams/Tests/Action/Element/HeaderTest.php b/src/Symfony/Component/Notifier/Bridge/MicrosoftTeams/Tests/Action/Element/HeaderTest.php new file mode 100644 index 0000000000000..e6677e6acaca2 --- /dev/null +++ b/src/Symfony/Component/Notifier/Bridge/MicrosoftTeams/Tests/Action/Element/HeaderTest.php @@ -0,0 +1,25 @@ +name($value = 'My name'); + + $this->assertSame($value, $action->toArray()['name']); + } + + public function testValue() + { + $action = (new Header()) + ->value($value = 'The value...'); + + $this->assertSame($value, $action->toArray()['value']); + } +} diff --git a/src/Symfony/Component/Notifier/Bridge/MicrosoftTeams/Tests/Action/HttpPostActionTest.php b/src/Symfony/Component/Notifier/Bridge/MicrosoftTeams/Tests/Action/HttpPostActionTest.php new file mode 100644 index 0000000000000..cd1794b1662a1 --- /dev/null +++ b/src/Symfony/Component/Notifier/Bridge/MicrosoftTeams/Tests/Action/HttpPostActionTest.php @@ -0,0 +1,70 @@ +name($value = 'My name'); + + $this->assertSame($value, $action->toArray()['name']); + } + + public function testTarget() + { + $action = (new HttpPostAction()) + ->target($value = 'https://symfony.com'); + + $this->assertSame($value, $action->toArray()['target']); + } + + public function testHeader() + { + $header = (new Header()) + ->name($name = 'Header-Name') + ->value($value = 'Header-Value'); + + $action = (new HttpPostAction()) + ->header($header); + + $this->assertCount(1, $action->toArray()['headers']); + $this->assertSame( + [ + ['name' => $name, 'value' => $value], + ], + $action->toArray()['headers'] + ); + } + + public function testBody() + { + $action = (new HttpPostAction()) + ->body($value = 'content'); + + $this->assertSame($value, $action->toArray()['body']); + } + + public function testBodyContentType() + { + $action = (new HttpPostAction()) + ->bodyContentType($value = 'application/json'); + + $this->assertSame($value, $action->toArray()['bodyContentType']); + } + + public function testToArray() + { + $this->assertSame( + [ + '@type' => 'HttpPOST', + ], + (new HttpPostAction())->toArray() + ); + } +} diff --git a/src/Symfony/Component/Notifier/Bridge/MicrosoftTeams/Tests/Action/Input/DateInputTest.php b/src/Symfony/Component/Notifier/Bridge/MicrosoftTeams/Tests/Action/Input/DateInputTest.php new file mode 100644 index 0000000000000..6cb843177cf99 --- /dev/null +++ b/src/Symfony/Component/Notifier/Bridge/MicrosoftTeams/Tests/Action/Input/DateInputTest.php @@ -0,0 +1,44 @@ +createInput() + ->includeTime(true); + + $this->assertTrue($input->toArray()['includeTime']); + } + + public function testIncludeTimeWithFalse() + { + $input = $this->createInput() + ->includeTime(false); + + $this->assertFalse($input->toArray()['includeTime']); + } + + public function testToArray() + { + $this->assertSame( + [ + '@type' => 'DateInput', + ], + $this->createInput()->toArray() + ); + } +} diff --git a/src/Symfony/Component/Notifier/Bridge/MicrosoftTeams/Tests/Action/Input/MultiChoiceInputTest.php b/src/Symfony/Component/Notifier/Bridge/MicrosoftTeams/Tests/Action/Input/MultiChoiceInputTest.php new file mode 100644 index 0000000000000..6cdded3eaa58b --- /dev/null +++ b/src/Symfony/Component/Notifier/Bridge/MicrosoftTeams/Tests/Action/Input/MultiChoiceInputTest.php @@ -0,0 +1,88 @@ +createInput() + ->choice($display = 'DISPLAY', $value = 'VALUE'); + + $this->assertSame( + [ + ['display' => $display, 'value' => $value], + ], + $input->toArray()['choices'] + ); + } + + public function testIsMultiSelectWithTrue() + { + $input = $this->createInput() + ->isMultiSelect(true); + + $this->assertTrue($input->toArray()['isMultiSelect']); + } + + public function testIsMultiSelectWithFalse() + { + $input = $this->createInput() + ->isMultiSelect(false); + + $this->assertFalse($input->toArray()['isMultiSelect']); + } + + /** + * @dataProvider styles + */ + public function testStyle(string $value) + { + $input = $this->createInput() + ->style($value); + + $this->assertSame($value, $input->toArray()['style']); + } + + /** + * @return \Generator + */ + public function styles(): \Generator + { + yield 'style-expanded' => ['expanded']; + yield 'style-normal' => ['normal']; + } + + /** + * @dataProvider styles + */ + public function testStyleThrowsWithUnknownStyle() + { + $this->expectException(InvalidArgumentException::class); + + $this->createInput()->style('red'); + } + + public function testToArray() + { + $this->assertSame( + [ + '@type' => 'MultichoiceInput', + ], + $this->createInput()->toArray() + ); + } +} diff --git a/src/Symfony/Component/Notifier/Bridge/MicrosoftTeams/Tests/Action/Input/TextInputTest.php b/src/Symfony/Component/Notifier/Bridge/MicrosoftTeams/Tests/Action/Input/TextInputTest.php new file mode 100644 index 0000000000000..fa958ec298cd3 --- /dev/null +++ b/src/Symfony/Component/Notifier/Bridge/MicrosoftTeams/Tests/Action/Input/TextInputTest.php @@ -0,0 +1,52 @@ +createInput() + ->isMultiline(true); + + $this->assertTrue($input->toArray()['isMultiline']); + } + + public function testIsMultilineWithFalse() + { + $input = $this->createInput() + ->isMultiline(false); + + $this->assertFalse($input->toArray()['isMultiline']); + } + + public function testMaxLength() + { + $input = $this->createInput() + ->maxLength($value = 10); + + $this->assertSame($value, $input->toArray()['maxLength']); + } + + public function testToArray() + { + $this->assertSame( + [ + '@type' => 'TextInput', + ], + $this->createInput()->toArray() + ); + } +} diff --git a/src/Symfony/Component/Notifier/Bridge/MicrosoftTeams/Tests/Action/InvokeAddInCommandActionTest.php b/src/Symfony/Component/Notifier/Bridge/MicrosoftTeams/Tests/Action/InvokeAddInCommandActionTest.php new file mode 100644 index 0000000000000..b877793f57cca --- /dev/null +++ b/src/Symfony/Component/Notifier/Bridge/MicrosoftTeams/Tests/Action/InvokeAddInCommandActionTest.php @@ -0,0 +1,55 @@ +name($value = 'My name'); + + $this->assertSame($value, $action->toArray()['name']); + } + + public function testAddInId() + { + $action = (new InvokeAddInCommandAction()) + ->addInId($value = '1234'); + + $this->assertSame($value, $action->toArray()['addInId']); + } + + public function testDesktopCommandId() + { + $action = (new InvokeAddInCommandAction()) + ->desktopCommandId($value = '324'); + + $this->assertSame($value, $action->toArray()['desktopCommandId']); + } + + public function testInitializationContext() + { + $value = [ + 'foo' => 'bar', + ]; + + $action = (new InvokeAddInCommandAction()) + ->initializationContext($value); + + $this->assertSame($value, $action->toArray()['initializationContext']); + } + + public function testToArray() + { + $this->assertSame( + [ + '@type' => 'InvokeAddInCommand', + ], + (new InvokeAddInCommandAction())->toArray() + ); + } +} diff --git a/src/Symfony/Component/Notifier/Bridge/MicrosoftTeams/Tests/Action/OpenUriActionTest.php b/src/Symfony/Component/Notifier/Bridge/MicrosoftTeams/Tests/Action/OpenUriActionTest.php new file mode 100644 index 0000000000000..458f25b6e20a0 --- /dev/null +++ b/src/Symfony/Component/Notifier/Bridge/MicrosoftTeams/Tests/Action/OpenUriActionTest.php @@ -0,0 +1,75 @@ +name($value = 'My name'); + + $this->assertSame($value, $action->toArray()['name']); + } + + public function testTargetWithDefaultValue() + { + $action = (new OpenUriAction()) + ->target($uri = 'URI'); + + $this->assertSame( + [ + ['os' => 'default', 'uri' => $uri], + ], + $action->toArray()['targets'] + ); + } + + /** + * @dataProvider operatingSystems + */ + public function testTarget(string $os) + { + $action = (new OpenUriAction()) + ->target($uri = 'URI', $os); + + $this->assertSame( + [ + ['os' => $os, 'uri' => $uri], + ], + $action->toArray()['targets'] + ); + } + + /** + * @return \Generator + */ + public function operatingSystems(): \Generator + { + yield 'os-android' => ['android']; + yield 'os-default' => ['default']; + yield 'os-ios' => ['iOS']; + yield 'os-windows' => ['windows']; + } + + public function testTargetThrowsWithUnknownOperatingSystem() + { + $this->expectException(InvalidArgumentException::class); + + (new OpenUriAction())->target('URI', 'FOO'); + } + + public function testToArray() + { + $this->assertSame( + [ + '@type' => 'OpenUri', + ], + (new OpenUriAction())->toArray() + ); + } +} diff --git a/src/Symfony/Component/Notifier/Bridge/MicrosoftTeams/Tests/MicrosoftTeamsOptionsTest.php b/src/Symfony/Component/Notifier/Bridge/MicrosoftTeams/Tests/MicrosoftTeamsOptionsTest.php new file mode 100644 index 0000000000000..3c766ffdb4592 --- /dev/null +++ b/src/Symfony/Component/Notifier/Bridge/MicrosoftTeams/Tests/MicrosoftTeamsOptionsTest.php @@ -0,0 +1,285 @@ +content($content = 'Content'); + + $this->assertSame( + [ + 'title' => $subject, + 'text' => $content, + '@type' => 'MessageCard', + '@context' => 'https://schema.org/extensions', + ], + (MicrosoftTeamsOptions::fromNotification($notification))->toArray() + ); + } + + public function testGetRecipientIdReturnsRecipientWhenSetViaConstructor() + { + $options = new MicrosoftTeamsOptions([ + 'recipient_id' => $recipient = '/webhook/foo', + ]); + + $this->assertSame($recipient, $options->getRecipientId()); + } + + public function testGetRecipientIdReturnsRecipientWhenSetSetter() + { + $options = (new MicrosoftTeamsOptions()) + ->recipient($recipient = '/webhook/foo'); + + $this->assertSame($recipient, $options->getRecipientId()); + } + + public function testGetRecipientIdReturnsNullIfNotSetViaConstructorAndSetter() + { + $options = new MicrosoftTeamsOptions(); + + $this->assertNull($options->getRecipientId()); + } + + public function testRecipientMethodThrowsIfValueDoesNotMatchRegex() + { + $options = new MicrosoftTeamsOptions(); + + $recipient = 'foo'; + + $this->expectException(InvalidArgumentException::class); + $this->expectExceptionMessage(sprintf('"%s" require recipient id format to be "/webhook/{uuid}@{uuid}/IncomingWebhook/{id}/{uuid}", "%s" given.', MicrosoftTeamsOptions::class, $recipient)); + + $options->recipient($recipient); + } + + public function testSummaryViaConstructor() + { + $options = new MicrosoftTeamsOptions([ + 'summary' => $summary = 'My summary', + ]); + + $this->assertSame($summary, $options->toArray()['summary']); + } + + public function testSummaryViaSetter() + { + $options = (new MicrosoftTeamsOptions()) + ->summary($summary = 'My summary'); + + $this->assertSame($summary, $options->toArray()['summary']); + } + + public function testTitleViaConstructor() + { + $options = new MicrosoftTeamsOptions([ + 'title' => $title = 'My title', + ]); + + $this->assertSame($title, $options->toArray()['title']); + } + + public function testTitleViaSetter() + { + $options = (new MicrosoftTeamsOptions()) + ->title($title = 'My title'); + + $this->assertSame($title, $options->toArray()['title']); + } + + public function testTextViaConstructor() + { + $options = new MicrosoftTeamsOptions([ + 'text' => $text = 'My text', + ]); + + $this->assertSame($text, $options->toArray()['text']); + } + + public function testTextViaSetter() + { + $options = (new MicrosoftTeamsOptions()) + ->text($text = 'My text'); + + $this->assertSame($text, $options->toArray()['text']); + } + + /** + * @dataProvider validThemeColors + */ + public function testThemeColorViaConstructor(string $themeColor) + { + $options = new MicrosoftTeamsOptions([ + 'themeColor' => $themeColor, + ]); + + $this->assertSame($themeColor, $options->toArray()['themeColor']); + } + + /** + * @dataProvider validThemeColors + */ + public function testThemeColorViaSetter(string $themeColor) + { + $options = (new MicrosoftTeamsOptions()) + ->themeColor($themeColor); + + $this->assertSame($themeColor, $options->toArray()['themeColor']); + } + + public function validThemeColors(): \Generator + { + yield ['#333']; + yield ['#333333']; + yield ['#fff']; + yield ['#ff0000']; + yield ['#FFF']; + yield ['#FF0000']; + } + + /** + * @dataProvider invalidThemeColors + */ + public function testThemeColorViaConstructorThrowsInvalidArgumentException(string $themeColor) + { + $this->expectException(InvalidArgumentException::class); + $this->expectExceptionMessage('MessageCard themeColor must have a valid hex color format.'); + + new MicrosoftTeamsOptions([ + 'themeColor' => $themeColor, + ]); + } + + /** + * @dataProvider invalidThemeColors + */ + public function testThemeColorViaSetterThrowsInvalidArgumentException(string $themeColor) + { + $this->expectException(InvalidArgumentException::class); + $this->expectExceptionMessage('MessageCard themeColor must have a valid hex color format.'); + + (new MicrosoftTeamsOptions()) + ->themeColor($themeColor); + } + + public function invalidThemeColors(): \Generator + { + yield ['']; + yield [' ']; + yield ['red']; + yield ['#1']; + yield ['#22']; + yield ['#4444']; + yield ['#55555']; + } + + public function testSectionViaConstructor() + { + $options = new MicrosoftTeamsOptions([ + 'sections' => $sections = [(new Section())->toArray()], + ]); + + $this->assertSame($sections, $options->toArray()['sections']); + } + + public function testSectionViaSetter() + { + $options = (new MicrosoftTeamsOptions()) + ->section($section = new Section()); + + $this->assertSame([$section->toArray()], $options->toArray()['sections']); + } + + public function testActionViaConstructor() + { + $options = new MicrosoftTeamsOptions([ + 'potentialAction' => $actions = [(new OpenUriAction())->toArray()], + ]); + + $this->assertSame($actions, $options->toArray()['potentialAction']); + } + + public function testActionViaSetter() + { + $options = (new MicrosoftTeamsOptions()) + ->action($action = new OpenUriAction()); + + $this->assertSame([$action->toArray()], $options->toArray()['potentialAction']); + } + + public function testActionViaConstructorThrowsIfMaxNumberOfActionsIsReached() + { + $this->expectException(InvalidArgumentException::class); + $this->expectExceptionMessage('MessageCard maximum number of "potentialAction" has been reached (4).'); + + new MicrosoftTeamsOptions([ + 'potentialAction' => [ + new OpenUriAction(), + new OpenUriAction(), + new OpenUriAction(), + new OpenUriAction(), + new OpenUriAction(), + ], + ]); + } + + public function testActionViaSetterThrowsIfMaxNumberOfActionsIsReached() + { + $options = (new MicrosoftTeamsOptions()) + ->action(new OpenUriAction()) + ->action(new OpenUriAction()) + ->action(new OpenUriAction()) + ->action(new OpenUriAction()); + + $this->expectException(InvalidArgumentException::class); + $this->expectExceptionMessage('MessageCard maximum number of "potentialAction" has been reached (4).'); + + $options->action(new OpenUriAction()); + } + + public function testExpectedActorViaConstructor() + { + $options = new MicrosoftTeamsOptions([ + 'expectedActors' => $expectedActors = ['Oskar'], + ]); + + $this->assertSame($expectedActors, $options->toArray()['expectedActors']); + } + + public function testExpectedActorViaSetter() + { + $options = (new MicrosoftTeamsOptions()) + ->expectedActor($expectedActor = 'Oskar'); + + $this->assertSame([$expectedActor], $options->toArray()['expectedActors']); + } + + public function testExpectedActorsViaConstructor() + { + $options = new MicrosoftTeamsOptions([ + 'expectedActors' => $expectedActors = ['Oskar', 'Fabien'], + ]); + + $this->assertSame($expectedActors, $options->toArray()['expectedActors']); + } + + public function testExpectedActorsViaSetter() + { + $options = (new MicrosoftTeamsOptions()) + ->expectedActor($expectedActor1 = 'Oskar') + ->expectedActor($expectedActor2 = 'Fabien') + ; + + $this->assertSame([$expectedActor1, $expectedActor2], $options->toArray()['expectedActors']); + } +} diff --git a/src/Symfony/Component/Notifier/Bridge/MicrosoftTeams/Tests/MicrosoftTeamsTransportTest.php b/src/Symfony/Component/Notifier/Bridge/MicrosoftTeams/Tests/MicrosoftTeamsTransportTest.php index a583bed135440..5c4517bd85b42 100644 --- a/src/Symfony/Component/Notifier/Bridge/MicrosoftTeams/Tests/MicrosoftTeamsTransportTest.php +++ b/src/Symfony/Component/Notifier/Bridge/MicrosoftTeams/Tests/MicrosoftTeamsTransportTest.php @@ -13,11 +13,13 @@ use Symfony\Component\HttpClient\MockHttpClient; use Symfony\Component\HttpClient\Response\MockResponse; +use Symfony\Component\Notifier\Bridge\MicrosoftTeams\MicrosoftTeamsOptions; use Symfony\Component\Notifier\Bridge\MicrosoftTeams\MicrosoftTeamsTransport; use Symfony\Component\Notifier\Exception\TransportException; use Symfony\Component\Notifier\Message\ChatMessage; use Symfony\Component\Notifier\Message\MessageInterface; use Symfony\Component\Notifier\Message\SmsMessage; +use Symfony\Component\Notifier\Notification\Notification; use Symfony\Component\Notifier\Test\TransportTestCase; use Symfony\Component\Notifier\Transport\TransportInterface; use Symfony\Contracts\HttpClient\HttpClientInterface; @@ -79,7 +81,9 @@ public function testSend() { $message = 'testMessage'; - $expectedBody = json_encode(['text' => $message]); + $expectedBody = json_encode([ + 'text' => $message, + ]); $client = new MockHttpClient(function (string $method, string $url, array $options = []) use ($expectedBody): ResponseInterface { $this->assertJsonStringEqualsJsonString($expectedBody, $options['body']); @@ -91,4 +95,73 @@ public function testSend() $transport->send(new ChatMessage($message)); } + + public function testSendWithOptionsAndTextOverwritesChatMessage() + { + $message = 'testMessage'; + $options = new MicrosoftTeamsOptions([ + 'text' => $optionsTextMessage = 'optionsTestMessage', + ]); + + $expectedBody = json_encode([ + 'text' => $optionsTextMessage, + ]); + + $client = new MockHttpClient(function (string $method, string $url, array $options = []) use ($expectedBody): ResponseInterface { + $this->assertJsonStringEqualsJsonString($expectedBody, $options['body']); + + return new MockResponse('1', ['response_headers' => ['request-id' => ['testRequestId']], 'http_code' => 200]); + }); + + $transport = $this->createTransport($client); + + $transport->send(new ChatMessage($message, $options)); + } + + public function testSendWithOptionsAsMessageCard() + { + $title = 'title'; + $message = 'testMessage'; + + $options = new MicrosoftTeamsOptions([ + 'title' => $title, + ]); + + $expectedBody = json_encode([ + '@context' => 'https://schema.org/extensions', + '@type' => 'MessageCard', + 'text' => $message, + 'title' => $title, + ]); + + $client = new MockHttpClient(function (string $method, string $url, array $options = []) use ($expectedBody): ResponseInterface { + $this->assertJsonStringEqualsJsonString($expectedBody, $options['body']); + + return new MockResponse('1', ['response_headers' => ['request-id' => ['testRequestId']], 'http_code' => 200]); + }); + + $transport = $this->createTransport($client); + + $transport->send(new ChatMessage($message, $options)); + } + + public function testSendFromNotification() + { + $notification = new Notification($message = 'testMessage'); + $chatMessage = ChatMessage::fromNotification($notification); + + $expectedBody = json_encode([ + 'text' => $message, + ]); + + $client = new MockHttpClient(function (string $method, string $url, array $options = []) use ($expectedBody): ResponseInterface { + $this->assertJsonStringEqualsJsonString($expectedBody, $options['body']); + + return new MockResponse('1', ['response_headers' => ['request-id' => ['testRequestId']], 'http_code' => 200]); + }); + + $transport = $this->createTransport($client); + + $transport->send($chatMessage); + } } diff --git a/src/Symfony/Component/Notifier/Bridge/MicrosoftTeams/Tests/Section/Field/ActivityTest.php b/src/Symfony/Component/Notifier/Bridge/MicrosoftTeams/Tests/Section/Field/ActivityTest.php new file mode 100644 index 0000000000000..3acd868cf87c3 --- /dev/null +++ b/src/Symfony/Component/Notifier/Bridge/MicrosoftTeams/Tests/Section/Field/ActivityTest.php @@ -0,0 +1,41 @@ +image($value = 'https://symfony.com/logo.png'); + + $this->assertSame($value, $field->toArray()['activityImage']); + } + + public function testTitle() + { + $field = (new Activity()) + ->title($value = 'Symfony is great!'); + + $this->assertSame($value, $field->toArray()['activityTitle']); + } + + public function testSubtitle() + { + $field = (new Activity()) + ->subtitle($value = 'I am a subtitle!'); + + $this->assertSame($value, $field->toArray()['activitySubtitle']); + } + + public function testText() + { + $field = (new Activity()) + ->text($value = 'Text goes here'); + + $this->assertSame($value, $field->toArray()['activityText']); + } +} diff --git a/src/Symfony/Component/Notifier/Bridge/MicrosoftTeams/Tests/Section/Field/FactTest.php b/src/Symfony/Component/Notifier/Bridge/MicrosoftTeams/Tests/Section/Field/FactTest.php new file mode 100644 index 0000000000000..17667c5c942e3 --- /dev/null +++ b/src/Symfony/Component/Notifier/Bridge/MicrosoftTeams/Tests/Section/Field/FactTest.php @@ -0,0 +1,25 @@ +name($value = 'Current version'); + + $this->assertSame($value, $field->toArray()['name']); + } + + public function testTitle() + { + $field = (new Fact()) + ->value($value = '5.3'); + + $this->assertSame($value, $field->toArray()['value']); + } +} diff --git a/src/Symfony/Component/Notifier/Bridge/MicrosoftTeams/Tests/Section/Field/ImageTest.php b/src/Symfony/Component/Notifier/Bridge/MicrosoftTeams/Tests/Section/Field/ImageTest.php new file mode 100644 index 0000000000000..bed326e6fbf85 --- /dev/null +++ b/src/Symfony/Component/Notifier/Bridge/MicrosoftTeams/Tests/Section/Field/ImageTest.php @@ -0,0 +1,25 @@ +image($value = 'https://symfony.com/logo.png'); + + $this->assertSame($value, $field->toArray()['image']); + } + + public function testTitle() + { + $field = (new Image()) + ->title($value = 'Symfony is great!'); + + $this->assertSame($value, $field->toArray()['title']); + } +} diff --git a/src/Symfony/Component/Notifier/Bridge/MicrosoftTeams/Tests/Section/SectionTest.php b/src/Symfony/Component/Notifier/Bridge/MicrosoftTeams/Tests/Section/SectionTest.php new file mode 100644 index 0000000000000..eb4cef597a9f9 --- /dev/null +++ b/src/Symfony/Component/Notifier/Bridge/MicrosoftTeams/Tests/Section/SectionTest.php @@ -0,0 +1,127 @@ +title($value = 'Symfony is great!'); + + $this->assertSame($value, $section->toArray()['title']); + } + + public function testText() + { + $section = (new Section()) + ->text($value = 'Community power is awesome!'); + + $this->assertSame($value, $section->toArray()['text']); + } + + /** + * @dataProvider allowedActions + */ + public function testAction(array $expected, ActionInterface $action) + { + $section = (new Section()) + ->action($action); + + $this->assertCount(1, $section->toArray()['potentialAction']); + $this->assertSame($expected, $section->toArray()['potentialAction']); + } + + public function allowedActions(): \Generator + { + yield [[['@type' => 'ActionCard']], new ActionCard()]; + yield [[['@type' => 'HttpPOST']], new HttpPostAction()]; + yield [[['@type' => 'InvokeAddInCommand']], new InvokeAddInCommandAction()]; + yield [[['@type' => 'OpenUri']], new OpenUriAction()]; + } + + public function testActivity() + { + $activity = (new Activity()) + ->image($imageUrl = 'https://symfony.com/logo.png') + ->title($title = 'Activities') + ->subtitle($subtitle = 'for Admins only') + ->text($text = 'Hey Symfony!'); + + $section = (new Section()) + ->activity($activity); + + $this->assertSame( + [ + 'activityImage' => $imageUrl, + 'activityTitle' => $title, + 'activitySubtitle' => $subtitle, + 'activityText' => $text, + ], + $section->toArray() + ); + } + + public function testImage() + { + $image = (new Image()) + ->image($imageUrl = 'https://symfony.com/logo.png') + ->title($title = 'Symfony logo'); + + $section = (new Section()) + ->image($image); + + $this->assertCount(1, $section->toArray()['images']); + $this->assertSame( + [ + ['image' => $imageUrl, 'title' => $title], + ], + $section->toArray()['images'] + ); + } + + public function testFact() + { + $fact = (new Fact()) + ->name($name = 'Current version') + ->value($value = '5.3'); + + $section = (new Section()) + ->fact($fact); + + $this->assertCount(1, $section->toArray()['facts']); + $this->assertSame( + [ + ['name' => $name, 'value' => $value], + ], + $section->toArray()['facts'] + ); + } + + public function testMarkdownWithTrue() + { + $action = (new Section()) + ->markdown(true); + + $this->assertTrue($action->toArray()['markdown']); + } + + public function testMarkdownWithFalse() + { + $action = (new Section()) + ->markdown(false); + + $this->assertFalse($action->toArray()['markdown']); + } +} pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy