Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 12 additions & 4 deletions src/DevToTarget.php
Original file line number Diff line number Diff line change
Expand Up @@ -60,11 +60,12 @@ public function push(array $post, ?string $externalId): array
$body,
);

if ($resp['status'] === 403) {
throw new SyndicationError('Dev.to rejected the article body (HTTP 403), likely raw HTML or SVG in the post.');
}
if ($resp['status'] < 200 || $resp['status'] >= 300) {
throw new SyndicationError('Dev.to returned HTTP ' . $resp['status'] . '.');
$hint = $resp['status'] === 403
? 'Dev.to rejected the request (HTTP 403), likely raw HTML/SVG in the post, or a key/permissions issue.'
: 'Dev.to returned HTTP ' . $resp['status'] . '.';
$detail = $this->snippet($resp['body']);
throw new SyndicationError($detail === '' ? $hint : $hint . ' Response: ' . $detail);
}
$data = json_decode($resp['body'], true);
$data = is_array($data) ? $data : [];
Expand All @@ -75,6 +76,13 @@ public function push(array $post, ?string $externalId): array
];
}

/** A one-line, length-capped slice of the platform's response, for a diagnosable error. */
private function snippet(string $body): string
{
$body = trim((string) preg_replace('/\s+/', ' ', $body));
return mb_strlen($body) > 300 ? mb_substr($body, 0, 297) . '...' : $body;
}

/**
* Dev.to tags must be lowercase alphanumeric and it accepts at most four.
*
Expand Down
16 changes: 12 additions & 4 deletions src/HashnodeTarget.php
Original file line number Diff line number Diff line change
Expand Up @@ -90,11 +90,12 @@ public function push(array $post, ?string $externalId): array
'Accept' => 'application/json',
], $body);

if ($resp['status'] === 403) {
throw new SyndicationError('Hashnode rejected the request (HTTP 403), likely raw HTML or SVG in the post, or a token without Pro access.');
}
if ($resp['status'] < 200 || $resp['status'] >= 300) {
throw new SyndicationError('Hashnode returned HTTP ' . $resp['status'] . '.');
$hint = $resp['status'] === 403
? 'Hashnode rejected the request (HTTP 403), likely raw HTML/SVG in the post, or a token/permissions issue (API writes need Pro).'
: 'Hashnode returned HTTP ' . $resp['status'] . '.';
$detail = $this->snippet($resp['body']);
throw new SyndicationError($detail === '' ? $hint : $hint . ' Response: ' . $detail);
}

$data = json_decode($resp['body'], true);
Expand All @@ -115,6 +116,13 @@ public function push(array $post, ?string $externalId): array
];
}

/** A one-line, length-capped slice of the platform's response, for a diagnosable error. */
private function snippet(string $body): string
{
$body = trim((string) preg_replace('/\s+/', ' ', $body));
return mb_strlen($body) > 300 ? mb_substr($body, 0, 297) . '...' : $body;
}

/**
* Hashnode wants tags as {name, slug} objects: the slug is lowercase, hyphenated,
* alphanumeric; the name keeps the author's wording. It recommends at most five.
Expand Down
17 changes: 11 additions & 6 deletions tests/DevToTargetTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -52,13 +52,18 @@ public function test_a_non_2xx_becomes_a_clear_error(): void
(new DevToTarget(new FakeHttpClient(422, '{"error":"nope"}'), 'k'))->push($this->post(), null);
}

public function test_a_403_is_translated_to_a_body_rejection_message(): void
public function test_a_403_message_is_open_about_the_cause_and_surfaces_the_response(): void
{
// Forem answers 403 when it rejects the body (e.g. raw HTML/SVG); the message
// must point there, not at the API key.
$this->expectException(SyndicationError::class);
$this->expectExceptionMessage('raw HTML or SVG');
(new DevToTarget(new FakeHttpClient(403, '{}'), 'k'))->push($this->post(), null);
// A 403 is not necessarily the body: it may be a key/permissions issue. The
// message must say so, and carry the raw platform response so it is diagnosable.
try {
(new DevToTarget(new FakeHttpClient(403, '{"error":"you are not allowed"}'), 'k'))->push($this->post(), null);
self::fail('expected a SyndicationError');
} catch (SyndicationError $e) {
self::assertStringContainsString('HTTP 403', $e->getMessage());
self::assertStringContainsString('key/permissions', $e->getMessage());
self::assertStringContainsString('you are not allowed', $e->getMessage(), 'raw response is surfaced');
}
}

public function test_unconfigured_reports_and_refuses(): void
Expand Down
12 changes: 8 additions & 4 deletions tests/HashnodeTargetTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -76,11 +76,15 @@ public function test_a_non_2xx_becomes_a_clear_error(): void
$this->target(new FakeHttpClient(500, ''))->push($this->post(), null);
}

public function test_a_403_is_translated_to_a_clear_message(): void
public function test_a_403_message_is_open_about_the_cause_and_surfaces_the_response(): void
{
$this->expectException(SyndicationError::class);
$this->expectExceptionMessage('raw HTML or SVG');
$this->target(new FakeHttpClient(403, ''))->push($this->post(), null);
try {
$this->target(new FakeHttpClient(403, '{"message":"forbidden detail"}'))->push($this->post(), null);
self::fail('expected a SyndicationError');
} catch (SyndicationError $e) {
self::assertStringContainsString('HTTP 403', $e->getMessage());
self::assertStringContainsString('forbidden detail', $e->getMessage(), 'raw response is surfaced');
}
}

public function test_unconfigured_without_a_publication_reports_and_refuses(): void
Expand Down
Loading