• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

LeTraceurSnorkLibrary / MessSaga / 24455793340

15 Apr 2026 12:57PM UTC coverage: 39.248% (+2.7%) from 36.562%
24455793340

Pull #20

github

web-flow
Merge 4e3783ced into 4ac78237e
Pull Request #20: feat: tariffs

161 of 280 new or added lines in 14 files covered. (57.5%)

2 existing lines in 2 files now uncovered.

741 of 1888 relevant lines covered (39.25%)

0.78 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

87.34
/app/Services/Import/MessagePreparationService.php
1
<?php
2

3
declare(strict_types=1);
4

5
namespace App\Services\Import;
6

7
use App\Models\MediaTypes\SupportedMediaTypesEnum;
8
use App\Models\Message;
9
use App\Services\Import\DTO\PreparedMessageRowResult;
10
use App\Services\Media\ImportedMediaResolverService;
11
use App\Services\Media\Storage\MediaStorageInterface;
12
use Carbon\Carbon;
13
use Exception;
14
use Illuminate\Database\Eloquent\Model;
15
use Illuminate\Support\Facades\Crypt;
16

17
class MessagePreparationService
18
{
19
    /**
20
     * @param ImportedMediaResolverService $importedMediaResolverService
21
     * @param MediaStorageInterface        $mediaStorage
22
     */
23
    public function __construct(
24
        private readonly ImportedMediaResolverService $importedMediaResolverService,
25
        private readonly MediaStorageInterface $mediaStorage
26
    ) {
27
    }
8✔
28

29
    /**
30
     * @param mixed $externalId
31
     *
32
     * @return string|null
33
     */
34
    public function normalizeExternalId(mixed $externalId): ?string
35
    {
36
        if (!is_scalar($externalId)) {
2✔
37
            return null;
1✔
38
        }
39

40
        $normalized = trim((string)$externalId);
2✔
41

42
        return $normalized !== ''
2✔
43
            ? $normalized
1✔
44
            : null;
2✔
45
    }
46

47
    /**
48
     * @param array<string, mixed> $message
49
     *
50
     * @return string
51
     */
52
    public function buildDeduplicationHash(array $message): string
53
    {
54
        $sentAt = $message['sent_at'] ?? '';
2✔
55
        if ($sentAt instanceof Carbon) {
2✔
56
            $sentAtFormatted = $sentAt->format('Y-m-d H:i:s');
1✔
57
        } elseif (is_string($sentAt)) {
1✔
58
            try {
59
                $sentAtFormatted = Carbon::parse($sentAt)->format('Y-m-d H:i:s');
1✔
60
            } catch (Exception) {
1✔
61
                $sentAtFormatted = $sentAt;
1✔
62
            }
63
        } else {
64
            $sentAtFormatted = (string)$sentAt;
×
65
        }
66

67
        return hash(
2✔
68
            'sha256',
2✔
69
            $sentAtFormatted
2✔
70
            . ($message['text'] ?? '')
2✔
71
            . ($message['sender_name'] ?? '')
2✔
72
            . ($message['sender_external_id'] ?? '')
2✔
73
        );
2✔
74
    }
75

76
    /**
77
     * @param string|null          $mediaRootPath
78
     * @param array<string, mixed> $message
79
     * @param int                  $conversationId
80
     *
81
     * @return string|null
82
     */
83
    public function copyAttachmentForMessage(
84
        ?string $mediaRootPath,
85
        array   $message,
86
        int     $conversationId
87
    ): ?string {
88
        if ($mediaRootPath === null || empty($message['attachment_export_path'])) {
2✔
89
            return null;
1✔
90
        }
91

92
        return $this->importedMediaResolverService->copyForConversation(
1✔
93
            $mediaRootPath,
1✔
94
            (string)$message['attachment_export_path'],
1✔
95
            $conversationId
1✔
96
        );
1✔
97
    }
98

99
    /**
100
     * @param string|null          $mediaRootPath
101
     * @param array<string, mixed> $message
102
     *
103
     * @return int|null
104
     */
105
    public function estimateAttachmentSizeBytes(
106
        ?string $mediaRootPath,
107
        array $message
108
    ): ?int {
NEW
109
        if ($mediaRootPath === null || empty($message['attachment_export_path'])) {
×
NEW
110
            return null;
×
111
        }
112

NEW
113
        return $this->importedMediaResolverService->estimateAttachmentSizeBytes(
×
NEW
114
            $mediaRootPath,
×
NEW
115
            (string)$message['attachment_export_path']
×
NEW
116
        );
×
117
    }
118

119
    /**
120
     * @param array<string, mixed>  $message
121
     * @param int                   $conversationId
122
     * @param class-string<Message> $messageModelClass
123
     * @param string|null           $attachmentStoredPath
124
     *
125
     * @return PreparedMessageRowResult
126
     */
127
    public function prepareMessageRowForInsert(
128
        array   $message,
129
        int     $conversationId,
130
        string  $messageModelClass,
131
        ?string $attachmentStoredPath = null,
132
        ?int $attachmentSizeBytes = null
133
    ): PreparedMessageRowResult {
134
        $exportRaw        = $message['attachment_export_path'] ?? null;
2✔
135
        $exportNormalized = $this->normalizeExportPath($exportRaw);
2✔
136

137
        unset($message['attachment_export_path'], $message['attachment_stored_path']);
2✔
138

139
        $text = $message['text'] ?? null;
2✔
140

141
        $sentAt = $message['sent_at'] ?? null;
2✔
142
        if ($sentAt instanceof Carbon) {
2✔
143
            $sentAt = $sentAt->format('Y-m-d H:i:s');
1✔
144
        }
145

146
        $row = array_merge($message, [
2✔
147
            'conversation_id' => $conversationId,
2✔
148
            'sent_at'         => $sentAt,
2✔
149
            'text'            => $text
2✔
150
                ? Crypt::encryptString($text)
2✔
151
                : null,
152
        ]);
2✔
153

154
        /**
155
         * @var Model $model
156
         */
157
        $model   = $messageModelClass::make();
2✔
158
        $allowed = $model->getFillable();
2✔
159
        $row     = array_merge(
2✔
160
            array_fill_keys($allowed, null),
2✔
161
            array_intersect_key($row, array_flip($allowed))
2✔
162
        );
2✔
163

164
        $mediaPayload = null;
2✔
165
        if ($exportNormalized !== null || $attachmentStoredPath !== null) {
2✔
166
            $mime = null;
1✔
167
            if ($attachmentStoredPath !== null && $this->mediaStorage->exists($attachmentStoredPath)) {
1✔
168
                $mime = $this->mediaStorage->mimeType($attachmentStoredPath);
1✔
169
            }
170
            $mediaPayload = [
1✔
171
                'stored_path'       => $attachmentStoredPath,
1✔
172
                'export_path'       => $exportNormalized,
1✔
173
                'media_type'        => SupportedMediaTypesEnum::detect($mime, $exportNormalized)?->value,
1✔
174
                'mime_type'         => $mime,
1✔
175
                'original_filename' => $exportNormalized
1✔
176
                    ? basename(str_replace('\\', '/', $exportNormalized))
1✔
177
                    : ($attachmentStoredPath
×
178
                        ? basename($attachmentStoredPath)
×
179
                        : null),
180
                'size_bytes'        => $attachmentStoredPath !== null
1✔
181
                    ? max(0, (int)($attachmentSizeBytes ?? 0))
1✔
182
                    : 0,
1✔
183
            ];
1✔
184
        }
185

186
        return new PreparedMessageRowResult($row, $mediaPayload);
2✔
187
    }
188

189
    /**
190
     * @param mixed $exportRaw
191
     *
192
     * @return string|null
193
     */
194
    private function normalizeExportPath(mixed $exportRaw): ?string
195
    {
196
        if (!is_string($exportRaw)) {
2✔
197
            return null;
1✔
198
        }
199

200
        $normalized = trim(str_replace('\\', '/', $exportRaw));
1✔
201
        if ($normalized === '') {
1✔
202
            return null;
×
203
        }
204

205
        return $normalized;
1✔
206
    }
207
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc