Repository: sanic-org/sanic

Issue:
Headers from Exceptions
### Is there an existing issue for this?

- [X] I have searched the existing issues

### Describe the bug

Headers set on Exception objects not carried through on all renderers

### Code snippet

```py
raise Unauthorized(
    "Auth required.",
    headers={"foo": "bar"},
)
```

### Expected Behavior

Response should have:

```
Foo: bar
```

### How do you run Sanic?

Sanic CLI

### Operating System

all

### Sanic Version

23.3

### Additional context

_No response_

Discussion:
codecov[bot]: ## [Codecov](https://app.codecov.io/gh/sanic-org/sanic/pull/2774?src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=sanic-org) Report
Patch coverage: **`100.000`**% and project coverage change: **`-0.040`** :warning:
> Comparison is base [(`049983c`)](https://app.codecov.io/gh/sanic-org/sanic/commit/049983cb704ab9d439c5fddf23d0bf3784b53741?el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=sanic-org) 88.919% compared to head [(`d3ca08d`)](https://app.codecov.io/gh/sanic-org/sanic/pull/2774?src=pr&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=sanic-org) 88.879%.

> :exclamation: Current head d3ca08d differs from pull request most recent head dc349bc. Consider uploading reports for the commit dc349bc to get more accurate results

<details><summary>Additional details and impacted files</summary>


```diff
@@              Coverage Diff              @@
##              main     #2774       +/-   ##
=============================================
- Coverage   88.919%   88.879%   -0.040%     
=============================================
  Files           92        92               
  Lines         6976      6978        +2     
  Branches      1190      1190               
=============================================
- Hits          6203      6202        -1     
- Misses         529       531        +2     
- Partials       244       245        +1     
```


| [Impacted Files](https://app.codecov.io/gh/sanic-org/sanic/pull/2774?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=sanic-org) | Coverage Δ | |
|---|---|---|
| [sanic/errorpages.py](https://app.codecov.io/gh/sanic-org/sanic/pull/2774?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=sanic-org#diff-c2FuaWMvZXJyb3JwYWdlcy5weQ==) | `98.780% <100.000%> (+0.015%)` | :arrow_up: |

... and [2 files with indirect coverage changes](https://app.codecov.io/gh/sanic-org/sanic/pull/2774/indirect-changes?src=pr&el=tree-more&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=sanic-org)


</details>

[:umbrella: View full report in Codecov by Sentry](https://app.codecov.io/gh/sanic-org/sanic/pull/2774?src=pr&el=continue&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=sanic-org).   
:loudspeaker: Do you have feedback about the report comment? [Let us know in this issue](https://about.codecov.io/codecov-pr-comment-feedback/?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=sanic-org).


Patch:
diff --git a/sanic/errorpages.py b/sanic/errorpages.py
index 19ccc69b..7b243ddb 100644
--- a/sanic/errorpages.py
+++ b/sanic/errorpages.py
@@ -92,8 +92,10 @@ class BaseRenderer:
             self.full
             if self.debug and not getattr(self.exception, "quiet", False)
             else self.minimal
-        )
-        return output()
+        )()
+        output.status = self.status
+        output.headers.update(self.headers)
+        return output
 
     def minimal(self) -> HTTPResponse:  # noqa
         """
@@ -125,7 +127,7 @@ class HTMLRenderer(BaseRenderer):
             request=self.request,
             exc=self.exception,
         )
-        return html(page.render(), status=self.status, headers=self.headers)
+        return html(page.render())
 
     def minimal(self) -> HTTPResponse:
         return self.full()
@@ -146,8 +148,7 @@ class TextRenderer(BaseRenderer):
                 text=self.text,
                 bar=("=" * len(self.title)),
                 body=self._generate_body(full=True),
-            ),
-            status=self.status,
+            )
         )
 
     def minimal(self) -> HTTPResponse:
@@ -157,9 +158,7 @@ class TextRenderer(BaseRenderer):
                 text=self.text,
                 bar=("=" * len(self.title)),
                 body=self._generate_body(full=False),
-            ),
-            status=self.status,
-            headers=self.headers,
+            )
         )
 
     @property
@@ -218,11 +217,11 @@ class JSONRenderer(BaseRenderer):
 
     def full(self) -> HTTPResponse:
         output = self._generate_output(full=True)
-        return json(output, status=self.status, dumps=self.dumps)
+        return json(output, dumps=self.dumps)
 
     def minimal(self) -> HTTPResponse:
         output = self._generate_output(full=False)
-        return json(output, status=self.status, dumps=self.dumps)
+        return json(output, dumps=self.dumps)
 
     def _generate_output(self, *, full):
         output = {


Test patch:
diff --git a/tests/test_errorpages.py b/tests/test_errorpages.py
index d2c1fc7b..a2df90b3 100644
--- a/tests/test_errorpages.py
+++ b/tests/test_errorpages.py
@@ -527,3 +527,26 @@ def test_guess_mime_logging(
     ]
 
     assert logmsg == expected
+
+
+@pytest.mark.parametrize(
+    "format,expected",
+    (
+        ("html", "text/html; charset=utf-8"),
+        ("text", "text/plain; charset=utf-8"),
+        ("json", "application/json"),
+    ),
+)
+def test_exception_header_on_renderers(app: Sanic, format, expected):
+    app.config.FALLBACK_ERROR_FORMAT = format
+
+    @app.get("/test")
+    def test(request):
+        raise SanicException(
+            "test", status_code=400, headers={"exception": "test"}
+        )
+
+    _, response = app.test_client.get("/test")
+    assert response.status == 400
+    assert response.headers.get("exception") == "test"
+    assert response.content_type == expected