parent
							
								
									02f8a32b46
								
							
						
					
					
						commit
						534b93e142
					
				
					 13 changed files with 425 additions and 29 deletions
				
			
		
							
								
								
									
										2
									
								
								Makefile
									
										
									
									
									
								
							
							
						
						
									
										2
									
								
								Makefile
									
										
									
									
									
								
							| 
						 | 
				
			
			@ -50,7 +50,7 @@ docs: docs-deps
 | 
			
		|||
check: test fmt-check vet lint staticcheck
 | 
			
		||||
 | 
			
		||||
test: .PHONY
 | 
			
		||||
	$(GO) test ./...
 | 
			
		||||
	$(GO) test -v ./...
 | 
			
		||||
 | 
			
		||||
race: .PHONY
 | 
			
		||||
	$(GO) test -race ./...
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										155
									
								
								docs/publish.md
									
										
									
									
									
								
							
							
						
						
									
										155
									
								
								docs/publish.md
									
										
									
									
									
								
							| 
						 | 
				
			
			@ -30,6 +30,12 @@ Here's an example showing how to publish a simple message using a POST request:
 | 
			
		|||
        strings.NewReader("Backup successful 😀"))
 | 
			
		||||
    ```
 | 
			
		||||
 | 
			
		||||
=== "Python"
 | 
			
		||||
    ``` python
 | 
			
		||||
    requests.post("https://ntfy.sh/mytopic", 
 | 
			
		||||
        data="Backup successful 😀".encode(encoding='utf-8'))
 | 
			
		||||
    ```
 | 
			
		||||
 | 
			
		||||
=== "PHP"
 | 
			
		||||
    ``` php-inline
 | 
			
		||||
    file_get_contents('https://ntfy.sh/mytopic', false, stream_context_create([
 | 
			
		||||
| 
						 | 
				
			
			@ -95,6 +101,17 @@ a [title](#message-title), and [tag messages](#tags-emojis) 🥳 🎉. Here's an
 | 
			
		|||
	http.DefaultClient.Do(req)
 | 
			
		||||
    ```
 | 
			
		||||
 | 
			
		||||
=== "Python"
 | 
			
		||||
    ``` python
 | 
			
		||||
    requests.post("https://ntfy.sh/phil_alerts",
 | 
			
		||||
        data="Remote access to phils-laptop detected. Act right away.",
 | 
			
		||||
        headers={
 | 
			
		||||
            "Title": "Unauthorized access detected",
 | 
			
		||||
            "Priority": "urgent",
 | 
			
		||||
            "Tags": "warning,skull"
 | 
			
		||||
        })
 | 
			
		||||
    ```
 | 
			
		||||
 | 
			
		||||
=== "PHP"
 | 
			
		||||
    ``` php-inline
 | 
			
		||||
    file_get_contents('https://ntfy.sh/phil_alerts', false, stream_context_create([
 | 
			
		||||
| 
						 | 
				
			
			@ -151,6 +168,13 @@ you can set the `X-Title` header (or any of its aliases: `Title`, `ti`, or `t`).
 | 
			
		|||
    http.DefaultClient.Do(req)
 | 
			
		||||
    ```
 | 
			
		||||
 | 
			
		||||
=== "Python"
 | 
			
		||||
    ``` python
 | 
			
		||||
    requests.post("https://ntfy.sh/controversial",
 | 
			
		||||
        data="Oh my ...",
 | 
			
		||||
        headers={ "Title": "Dogs are better than cats" })
 | 
			
		||||
    ```
 | 
			
		||||
 | 
			
		||||
=== "PHP"
 | 
			
		||||
    ``` php-inline
 | 
			
		||||
    file_get_contents('https://ntfy.sh/controversial', false, stream_context_create([
 | 
			
		||||
| 
						 | 
				
			
			@ -217,6 +241,13 @@ You can set the priority with the header `X-Priority` (or any of its aliases: `P
 | 
			
		|||
    http.DefaultClient.Do(req)
 | 
			
		||||
    ```
 | 
			
		||||
 | 
			
		||||
=== "Python"
 | 
			
		||||
    ``` python
 | 
			
		||||
    requests.post("https://ntfy.sh/phil_alerts",
 | 
			
		||||
        data="An urgent message",
 | 
			
		||||
        headers={ "Priority": "5" })
 | 
			
		||||
    ```
 | 
			
		||||
 | 
			
		||||
=== "PHP"
 | 
			
		||||
    ``` php-inline
 | 
			
		||||
    file_get_contents('https://ntfy.sh/phil_alerts', false, stream_context_create([
 | 
			
		||||
| 
						 | 
				
			
			@ -314,6 +345,13 @@ them with a comma, e.g. `tag1,tag2,tag3`.
 | 
			
		|||
    http.DefaultClient.Do(req)
 | 
			
		||||
    ```
 | 
			
		||||
 | 
			
		||||
=== "Python"
 | 
			
		||||
    ``` python
 | 
			
		||||
    requests.post("https://ntfy.sh/backups",
 | 
			
		||||
        data="Backup of mailsrv13 failed",
 | 
			
		||||
        headers={ "Tags": "warning,mailsrv13,daily-backup" })
 | 
			
		||||
    ```
 | 
			
		||||
 | 
			
		||||
=== "PHP"
 | 
			
		||||
    ``` php-inline
 | 
			
		||||
    file_get_contents('https://ntfy.sh/backups', false, stream_context_create([
 | 
			
		||||
| 
						 | 
				
			
			@ -382,6 +420,13 @@ to be delivered in 3 days, it'll remain in the cache for 3 days and 12 hours. Al
 | 
			
		|||
    http.DefaultClient.Do(req)
 | 
			
		||||
    ```
 | 
			
		||||
 | 
			
		||||
=== "Python"
 | 
			
		||||
    ``` python
 | 
			
		||||
    requests.post("https://ntfy.sh/hello",
 | 
			
		||||
        data="Good morning",
 | 
			
		||||
        headers={ "At": "tomorrow, 10am" })
 | 
			
		||||
    ```
 | 
			
		||||
 | 
			
		||||
=== "PHP"
 | 
			
		||||
    ``` php-inline
 | 
			
		||||
    file_get_contents('https://ntfy.sh/backups', false, stream_context_create([
 | 
			
		||||
| 
						 | 
				
			
			@ -397,7 +442,6 @@ to be delivered in 3 days, it'll remain in the cache for 3 days and 12 hours. Al
 | 
			
		|||
 | 
			
		||||
Here are a few examples (assuming today's date is **12/10/2021, 9am, Eastern Time Zone**):
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
<table class="remove-md-box"><tr>
 | 
			
		||||
<td>
 | 
			
		||||
    <table><thead><tr><th><code>Delay/At/In</code> header</th><th>Message will be delivered at</th><th>Explanation</th></tr></thead><tbody>
 | 
			
		||||
| 
						 | 
				
			
			@ -411,6 +455,87 @@ Here are a few examples (assuming today's date is **12/10/2021, 9am, Eastern Tim
 | 
			
		|||
</td>
 | 
			
		||||
</tr></table>
 | 
			
		||||
 | 
			
		||||
## Webhooks (Send via GET) 
 | 
			
		||||
In addition to using PUT/POST, you can also send to topics via simple HTTP GET requests. This makes it easy to use 
 | 
			
		||||
a ntfy topic as a [webhook](https://en.wikipedia.org/wiki/Webhook), or if your client has limited HTTP support (e.g.
 | 
			
		||||
like the [MacroDroid](https://play.google.com/store/apps/details?id=com.arlosoft.macrodroid) Android app).
 | 
			
		||||
 | 
			
		||||
To send messages via HTTP GET, simply call the `/publish` endpoint (or its aliases `/send` and `/trigger`). Without 
 | 
			
		||||
any arguments, this will send the message `triggered` to the topic. However, you can provide all arguments that are 
 | 
			
		||||
also supported as HTTP headers as URL-encoded arguments. Be sure to check the list of all 
 | 
			
		||||
[supported parameters and headers](#list-of-all-parameters) for details.
 | 
			
		||||
 | 
			
		||||
For instance, assuming your topic is `mywebhook`, you can simply call `/mywebhook/trigger` to send a message 
 | 
			
		||||
(aka trigger the webhook):
 | 
			
		||||
 | 
			
		||||
=== "Command line (curl)"
 | 
			
		||||
    ```
 | 
			
		||||
    curl ntfy.sh/mywebhook/trigger
 | 
			
		||||
    ```
 | 
			
		||||
 | 
			
		||||
=== "HTTP"
 | 
			
		||||
    ``` http
 | 
			
		||||
    GET /mywebhook/trigger HTTP/1.1
 | 
			
		||||
    Host: ntfy.sh
 | 
			
		||||
    ```
 | 
			
		||||
 | 
			
		||||
=== "JavaScript"
 | 
			
		||||
    ``` javascript
 | 
			
		||||
    fetch('https://ntfy.sh/mywebhook/trigger')
 | 
			
		||||
    ```
 | 
			
		||||
 | 
			
		||||
=== "Go"
 | 
			
		||||
    ``` go
 | 
			
		||||
    http.Get("https://ntfy.sh/mywebhook/trigger")
 | 
			
		||||
    ```
 | 
			
		||||
 | 
			
		||||
=== "Python"
 | 
			
		||||
    ``` python
 | 
			
		||||
    requests.get("https://ntfy.sh/mywebhook/trigger")
 | 
			
		||||
    ```
 | 
			
		||||
 | 
			
		||||
=== "PHP"
 | 
			
		||||
    ``` php-inline
 | 
			
		||||
    file_get_contents('https://ntfy.sh/mywebhook/trigger');
 | 
			
		||||
    ```
 | 
			
		||||
 | 
			
		||||
To add a custom message, simply append the `message=` URL parameter. And of course you can set the 
 | 
			
		||||
[message priority](#message-priority), the [message title](#message-title), and [tags](#tags-emojis) as well. 
 | 
			
		||||
For a full list of possible parameters, check the list of [supported parameters and headers](#list-of-all-parameters).
 | 
			
		||||
 | 
			
		||||
Here's an example with a custom message, tags and a priority:
 | 
			
		||||
 | 
			
		||||
=== "Command line (curl)"
 | 
			
		||||
    ```
 | 
			
		||||
    curl "ntfy.sh/mywebhook/publish?message=Webhook+triggered&priority=high&tags=warning,skull"
 | 
			
		||||
    ```
 | 
			
		||||
 | 
			
		||||
=== "HTTP"
 | 
			
		||||
    ``` http
 | 
			
		||||
    GET /mywebhook/publish?message=Webhook+triggered&priority=high&tags=warning,skull HTTP/1.1
 | 
			
		||||
    Host: ntfy.sh
 | 
			
		||||
    ```
 | 
			
		||||
 | 
			
		||||
=== "JavaScript"
 | 
			
		||||
    ``` javascript
 | 
			
		||||
    fetch('https://ntfy.sh/mywebhook/publish?message=Webhook+triggered&priority=high&tags=warning,skull')
 | 
			
		||||
    ```
 | 
			
		||||
 | 
			
		||||
=== "Go"
 | 
			
		||||
    ``` go
 | 
			
		||||
    http.Get("https://ntfy.sh/mywebhook/publish?message=Webhook+triggered&priority=high&tags=warning,skull")
 | 
			
		||||
    ```
 | 
			
		||||
 | 
			
		||||
=== "Python"
 | 
			
		||||
    ``` python
 | 
			
		||||
    requests.get("https://ntfy.sh/mywebhook/publish?message=Webhook+triggered&priority=high&tags=warning,skull")
 | 
			
		||||
    ```
 | 
			
		||||
 | 
			
		||||
=== "PHP"
 | 
			
		||||
    ``` php-inline
 | 
			
		||||
    file_get_contents('https://ntfy.sh/mywebhook/publish?message=Webhook+triggered&priority=high&tags=warning,skull');
 | 
			
		||||
    ```
 | 
			
		||||
 | 
			
		||||
## Advanced features
 | 
			
		||||
 | 
			
		||||
### Message caching
 | 
			
		||||
| 
						 | 
				
			
			@ -459,6 +584,13 @@ are still delivered to connected subscribers, but [`since=`](subscribe/api.md#fe
 | 
			
		|||
    http.DefaultClient.Do(req)
 | 
			
		||||
    ```
 | 
			
		||||
 | 
			
		||||
=== "Python"
 | 
			
		||||
    ``` python
 | 
			
		||||
    requests.post("https://ntfy.sh/mytopic",
 | 
			
		||||
        data="This message won't be stored server-side",
 | 
			
		||||
        headers={ "Cache": "no" })
 | 
			
		||||
    ```
 | 
			
		||||
 | 
			
		||||
=== "PHP"
 | 
			
		||||
    ``` php-inline
 | 
			
		||||
    file_get_contents('https://ntfy.sh/mytopic', false, stream_context_create([
 | 
			
		||||
| 
						 | 
				
			
			@ -517,6 +649,13 @@ to `no`. This will instruct the server not to forward messages to Firebase.
 | 
			
		|||
    http.DefaultClient.Do(req)
 | 
			
		||||
    ```
 | 
			
		||||
 | 
			
		||||
=== "Python"
 | 
			
		||||
    ``` python
 | 
			
		||||
    requests.post("https://ntfy.sh/mytopic",
 | 
			
		||||
        data="This message won't be forwarded to FCM",
 | 
			
		||||
        headers={ "Firebase": "no" })
 | 
			
		||||
    ```
 | 
			
		||||
 | 
			
		||||
=== "PHP"
 | 
			
		||||
    ``` php-inline
 | 
			
		||||
    file_get_contents('https://ntfy.sh/mytopic', false, stream_context_create([
 | 
			
		||||
| 
						 | 
				
			
			@ -529,3 +668,17 @@ to `no`. This will instruct the server not to forward messages to Firebase.
 | 
			
		|||
        ]
 | 
			
		||||
    ]));
 | 
			
		||||
    ```
 | 
			
		||||
 | 
			
		||||
## List of all parameters
 | 
			
		||||
The following is a list of all parameters that can be passed when publishing a message. Parameter names are **case-insensitive**,
 | 
			
		||||
and can be passed as **HTTP headers** or **query parameters in the URL**.
 | 
			
		||||
 | 
			
		||||
| Parameter | Aliases (case-insensitive) | Description |
 | 
			
		||||
|---|---|---|
 | 
			
		||||
| `X-Message` | `Message`, `m` | Main body of the message as shown in the notification |
 | 
			
		||||
| `X-Title` | `Title`, `t` | [Message title](#message-title) |
 | 
			
		||||
| `X-Priority` | `Priority`, `prio`, `p` | [Message priority](#message-priority) |
 | 
			
		||||
| `X-Tags` | `Tags`, `ta` | [Tags and emojis](#tags-emojis) |
 | 
			
		||||
| `X-Delay` | `Delay`, `X-At`, `At`, `X-In`, `In` | Timestamp or duration for [delayed delivery](#scheduled-delivery) |
 | 
			
		||||
| `X-Cache` | `Cache` | Allows disabling [message caching](#message-caching) |
 | 
			
		||||
| `X-Firebase` | `Firebase` | Allows disabling [sending to Firebase](#disable-firebase) |
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										12
									
								
								examples/publish-python/publish.py
									
										
									
									
									
										Executable file
									
								
							
							
						
						
									
										12
									
								
								examples/publish-python/publish.py
									
										
									
									
									
										Executable file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,12 @@
 | 
			
		|||
#!/usr/bin/env python3
 | 
			
		||||
 | 
			
		||||
import requests
 | 
			
		||||
 | 
			
		||||
resp = requests.get("https://ntfy.sh/mytopic/trigger",
 | 
			
		||||
    data="Backup successful 😀".encode(encoding='utf-8'),
 | 
			
		||||
    headers={
 | 
			
		||||
        "Priority": "high",
 | 
			
		||||
        "Tags": "warning,skull",
 | 
			
		||||
        "Title": "Hello there"
 | 
			
		||||
    })
 | 
			
		||||
resp.raise_for_status()
 | 
			
		||||
							
								
								
									
										10
									
								
								go.mod
									
										
									
									
									
								
							
							
						
						
									
										10
									
								
								go.mod
									
										
									
									
									
								
							| 
						 | 
				
			
			@ -2,8 +2,6 @@ module heckel.io/ntfy
 | 
			
		|||
 | 
			
		||||
go 1.17
 | 
			
		||||
 | 
			
		||||
replace github.com/olebedev/when => github.com/binwiederhier/when v0.0.1-binwiederhier2
 | 
			
		||||
 | 
			
		||||
require (
 | 
			
		||||
	cloud.google.com/go/firestore v1.6.1 // indirect
 | 
			
		||||
	cloud.google.com/go/storage v1.18.2 // indirect
 | 
			
		||||
| 
						 | 
				
			
			@ -11,12 +9,12 @@ require (
 | 
			
		|||
	github.com/BurntSushi/toml v0.4.1 // indirect
 | 
			
		||||
	github.com/cpuguy83/go-md2man/v2 v2.0.1 // indirect
 | 
			
		||||
	github.com/mattn/go-sqlite3 v1.14.9
 | 
			
		||||
	github.com/olebedev/when v0.0.0-20190311101825-c3b538a97254
 | 
			
		||||
	github.com/olebedev/when v0.0.0-20211212231525-59bd4edcf9d6
 | 
			
		||||
	github.com/stretchr/testify v1.7.0
 | 
			
		||||
	github.com/urfave/cli/v2 v2.3.0
 | 
			
		||||
	golang.org/x/oauth2 v0.0.0-20211104180415-d3ed0bb246c8 // indirect
 | 
			
		||||
	golang.org/x/time v0.0.0-20211116232009-f0f3c7e86c11
 | 
			
		||||
	google.golang.org/api v0.62.0
 | 
			
		||||
	google.golang.org/api v0.63.0
 | 
			
		||||
	gopkg.in/yaml.v2 v2.4.0 // indirect
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -39,12 +37,12 @@ require (
 | 
			
		|||
	github.com/russross/blackfriday/v2 v2.1.0 // indirect
 | 
			
		||||
	go.opencensus.io v0.23.0 // indirect
 | 
			
		||||
	golang.org/x/net v0.0.0-20210813160813-60bc85c4be6d // indirect
 | 
			
		||||
	golang.org/x/sys v0.0.0-20211205182925-97ca703d548d // indirect
 | 
			
		||||
	golang.org/x/sys v0.0.0-20211210111614-af8b64212486 // indirect
 | 
			
		||||
	golang.org/x/text v0.3.7 // indirect
 | 
			
		||||
	golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect
 | 
			
		||||
	google.golang.org/appengine v1.6.7 // indirect
 | 
			
		||||
	google.golang.org/genproto v0.0.0-20211208223120-3a66f561d7aa // indirect
 | 
			
		||||
	google.golang.org/grpc v1.42.0 // indirect
 | 
			
		||||
	google.golang.org/grpc v1.43.0 // indirect
 | 
			
		||||
	google.golang.org/protobuf v1.27.1 // indirect
 | 
			
		||||
	gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c // indirect
 | 
			
		||||
)
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										19
									
								
								go.sum
									
										
									
									
									
								
							
							
						
						
									
										19
									
								
								go.sum
									
										
									
									
									
								
							| 
						 | 
				
			
			@ -25,7 +25,6 @@ cloud.google.com/go v0.90.0/go.mod h1:kRX0mNRHe0e2rC6oNakvwQqzyDmg57xJ+SZU1eT2aD
 | 
			
		|||
cloud.google.com/go v0.93.3/go.mod h1:8utlLll2EF5XMAV15woO4lSbWQlk8rer9aLOfLh7+YI=
 | 
			
		||||
cloud.google.com/go v0.94.1/go.mod h1:qAlAugsXlC+JWO+Bke5vCtc9ONxjQT3drlTTnAplMW4=
 | 
			
		||||
cloud.google.com/go v0.97.0/go.mod h1:GF7l59pYBVlXQIBLx3a761cZ41F9bBH3JUlihCt2Udc=
 | 
			
		||||
cloud.google.com/go v0.98.0/go.mod h1:ua6Ush4NALrHk5QXDWnjvZHN93OuF0HfuEPq9I1X0cM=
 | 
			
		||||
cloud.google.com/go v0.99.0 h1:y/cM2iqGgGi5D5DQZl6D9STN/3dR/Vx5Mp8s752oJTY=
 | 
			
		||||
cloud.google.com/go v0.99.0/go.mod h1:w0Xx2nLzqWJPuozYQX+hFfCSI8WioryfRDzkoI/Y2ZA=
 | 
			
		||||
cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o=
 | 
			
		||||
| 
						 | 
				
			
			@ -60,8 +59,6 @@ github.com/BurntSushi/toml v0.4.1/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbi
 | 
			
		|||
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
 | 
			
		||||
github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU=
 | 
			
		||||
github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY=
 | 
			
		||||
github.com/binwiederhier/when v0.0.1-binwiederhier2 h1:BjQC7OQI4MK0vXeltn2BEuf0Tdh/M6YNh1JrepnVr2I=
 | 
			
		||||
github.com/binwiederhier/when v0.0.1-binwiederhier2/go.mod h1:DPucAeQGDPUzYUt+NaWw6qsF5SFapWWToxEiVDh2aV0=
 | 
			
		||||
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
 | 
			
		||||
github.com/census-instrumentation/opencensus-proto v0.3.0 h1:t/LhUZLVitR1Ow2YOnduCsavhwFUklBMoGVYUCqmCqk=
 | 
			
		||||
github.com/census-instrumentation/opencensus-proto v0.3.0/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
 | 
			
		||||
| 
						 | 
				
			
			@ -204,6 +201,8 @@ github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
 | 
			
		|||
github.com/lyft/protoc-gen-star v0.5.3/go.mod h1:V0xaHgaf5oCCqmcxYcWiDfTiKsZsRc87/1qhoTACD8w=
 | 
			
		||||
github.com/mattn/go-sqlite3 v1.14.9 h1:10HX2Td0ocZpYEjhilsuo6WWtUqttj2Kb0KtD86/KYA=
 | 
			
		||||
github.com/mattn/go-sqlite3 v1.14.9/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU=
 | 
			
		||||
github.com/olebedev/when v0.0.0-20211212231525-59bd4edcf9d6 h1:oDSPaYiL2dbjcArLrFS8ANtwgJMyOLzvQCZon+XmFsk=
 | 
			
		||||
github.com/olebedev/when v0.0.0-20211212231525-59bd4edcf9d6/go.mod h1:DPucAeQGDPUzYUt+NaWw6qsF5SFapWWToxEiVDh2aV0=
 | 
			
		||||
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
 | 
			
		||||
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
 | 
			
		||||
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
 | 
			
		||||
| 
						 | 
				
			
			@ -400,8 +399,8 @@ golang.org/x/sys v0.0.0-20210908233432-aa78b53d3365/go.mod h1:oPkhp1MJrh7nUepCBc
 | 
			
		|||
golang.org/x/sys v0.0.0-20210917161153-d61c044b1678/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 | 
			
		||||
golang.org/x/sys v0.0.0-20211007075335-d3039528d8ac/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 | 
			
		||||
golang.org/x/sys v0.0.0-20211124211545-fe61309f8881/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 | 
			
		||||
golang.org/x/sys v0.0.0-20211205182925-97ca703d548d h1:FjkYO/PPp4Wi0EAUOVLxePm7qVW4r4ctbWpURyuOD0E=
 | 
			
		||||
golang.org/x/sys v0.0.0-20211205182925-97ca703d548d/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 | 
			
		||||
golang.org/x/sys v0.0.0-20211210111614-af8b64212486 h1:5hpz5aRr+W1erYCL5JRhSUBJRph7l9XkNveoExlrKYk=
 | 
			
		||||
golang.org/x/sys v0.0.0-20211210111614-af8b64212486/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 | 
			
		||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
 | 
			
		||||
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
 | 
			
		||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
 | 
			
		||||
| 
						 | 
				
			
			@ -506,8 +505,8 @@ google.golang.org/api v0.57.0/go.mod h1:dVPlbZyBo2/OjBpmvNdpn2GRm6rPy75jyU7bmhdr
 | 
			
		|||
google.golang.org/api v0.58.0/go.mod h1:cAbP2FsxoGVNwtgNAmmn3y5G1TWAiVYRmg4yku3lv+E=
 | 
			
		||||
google.golang.org/api v0.59.0/go.mod h1:sT2boj7M9YJxZzgeZqXogmhfmRWDtPzT31xkieUbuZU=
 | 
			
		||||
google.golang.org/api v0.61.0/go.mod h1:xQRti5UdCmoCEqFxcz93fTl338AVqDgyaDRuOZ3hg9I=
 | 
			
		||||
google.golang.org/api v0.62.0 h1:PhGymJMXfGBzc4lBRmrx9+1w4w2wEzURHNGF/sD/xGc=
 | 
			
		||||
google.golang.org/api v0.62.0/go.mod h1:dKmwPCydfsad4qCH08MSdgWjfHOyfpd4VtDGgRFdavw=
 | 
			
		||||
google.golang.org/api v0.63.0 h1:n2bqqK895ygnBpdPDYetfy23K7fJ22wsrZKCyfuRkkA=
 | 
			
		||||
google.golang.org/api v0.63.0/go.mod h1:gs4ij2ffTRXwuzzgJl/56BdwJaA194ijkfn++9tDuPo=
 | 
			
		||||
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
 | 
			
		||||
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
 | 
			
		||||
google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
 | 
			
		||||
| 
						 | 
				
			
			@ -577,8 +576,6 @@ google.golang.org/genproto v0.0.0-20211008145708-270636b82663/go.mod h1:5CzLGKJ6
 | 
			
		|||
google.golang.org/genproto v0.0.0-20211016002631-37fc39342514/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=
 | 
			
		||||
google.golang.org/genproto v0.0.0-20211028162531-8db9c33dc351/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=
 | 
			
		||||
google.golang.org/genproto v0.0.0-20211118181313-81c1377c94b1/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=
 | 
			
		||||
google.golang.org/genproto v0.0.0-20211129164237-f09f9a12af12/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=
 | 
			
		||||
google.golang.org/genproto v0.0.0-20211203200212-54befc351ae9/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=
 | 
			
		||||
google.golang.org/genproto v0.0.0-20211206160659-862468c7d6e0/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=
 | 
			
		||||
google.golang.org/genproto v0.0.0-20211208223120-3a66f561d7aa h1:I0YcKz0I7OAhddo7ya8kMnvprhcWM045PmkBdMO9zN0=
 | 
			
		||||
google.golang.org/genproto v0.0.0-20211208223120-3a66f561d7aa/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=
 | 
			
		||||
| 
						 | 
				
			
			@ -608,8 +605,8 @@ google.golang.org/grpc v1.39.0/go.mod h1:PImNr+rS9TWYb2O4/emRugxiyHZ5JyHW5F+RPnD
 | 
			
		|||
google.golang.org/grpc v1.39.1/go.mod h1:PImNr+rS9TWYb2O4/emRugxiyHZ5JyHW5F+RPnDzfrE=
 | 
			
		||||
google.golang.org/grpc v1.40.0/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9KAK34=
 | 
			
		||||
google.golang.org/grpc v1.40.1/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9KAK34=
 | 
			
		||||
google.golang.org/grpc v1.42.0 h1:XT2/MFpuPFsEX2fWh3YQtHkZ+WYZFQRfaUgLZYj/p6A=
 | 
			
		||||
google.golang.org/grpc v1.42.0/go.mod h1:k+4IHHFw41K8+bbowsex27ge2rCb65oeWqe4jJ590SU=
 | 
			
		||||
google.golang.org/grpc v1.43.0 h1:Eeu7bZtDZ2DpRCsLhUlcrLnvYaMK1Gz86a+hMVvELmM=
 | 
			
		||||
google.golang.org/grpc v1.43.0/go.mod h1:k+4IHHFw41K8+bbowsex27ge2rCb65oeWqe4jJ590SU=
 | 
			
		||||
google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.1.0/go.mod h1:6Kw0yEErY5E/yWrBtf03jp27GLLJujG4z/JK95pnjjw=
 | 
			
		||||
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
 | 
			
		||||
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -76,7 +76,7 @@ var (
 | 
			
		|||
	jsonRegex  = regexp.MustCompile(`^/[-_A-Za-z0-9]{1,64}(,[-_A-Za-z0-9]{1,64})*/json$`)
 | 
			
		||||
	sseRegex   = regexp.MustCompile(`^/[-_A-Za-z0-9]{1,64}(,[-_A-Za-z0-9]{1,64})*/sse$`)
 | 
			
		||||
	rawRegex   = regexp.MustCompile(`^/[-_A-Za-z0-9]{1,64}(,[-_A-Za-z0-9]{1,64})*/raw$`)
 | 
			
		||||
	sendRegex  = regexp.MustCompile(`^/[-_A-Za-z0-9]{1,64}(,[-_A-Za-z0-9]{1,64})*/(send|trigger)$`)
 | 
			
		||||
	sendRegex  = regexp.MustCompile(`^/[-_A-Za-z0-9]{1,64}(,[-_A-Za-z0-9]{1,64})*/(publish|send|trigger)$`)
 | 
			
		||||
 | 
			
		||||
	staticRegex      = regexp.MustCompile(`^/static/.+`)
 | 
			
		||||
	docsRegex        = regexp.MustCompile(`^/docs(|/.*)$`)
 | 
			
		||||
| 
						 | 
				
			
			@ -311,13 +311,12 @@ func (s *Server) handlePublish(w http.ResponseWriter, r *http.Request, _ *visito
 | 
			
		|||
			return err
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	w.Header().Set("Content-Type", "application/json")
 | 
			
		||||
	w.Header().Set("Access-Control-Allow-Origin", "*") // CORS, allow cross-origin requests
 | 
			
		||||
	if err := json.NewEncoder(w).Encode(m); err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
	s.mu.Lock()
 | 
			
		||||
	s.messages++
 | 
			
		||||
	s.mu.Unlock()
 | 
			
		||||
	s.inc(&s.messages)
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -691,6 +690,12 @@ func (s *Server) visitor(r *http.Request) *visitor {
 | 
			
		|||
	return v
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (s *Server) inc(counter *int64) {
 | 
			
		||||
	s.mu.Lock()
 | 
			
		||||
	defer s.mu.Unlock()
 | 
			
		||||
	*counter++
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (s *Server) fail(w http.ResponseWriter, r *http.Request, code int, err error) {
 | 
			
		||||
	log.Printf("[%s] %s - %d - %s", r.RemoteAddr, r.Method, code, err.Error())
 | 
			
		||||
	w.WriteHeader(code)
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -4,10 +4,12 @@ import (
 | 
			
		|||
	"bufio"
 | 
			
		||||
	"context"
 | 
			
		||||
	"encoding/json"
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"github.com/stretchr/testify/require"
 | 
			
		||||
	"heckel.io/ntfy/config"
 | 
			
		||||
	"net/http"
 | 
			
		||||
	"net/http/httptest"
 | 
			
		||||
	"os"
 | 
			
		||||
	"path/filepath"
 | 
			
		||||
	"strings"
 | 
			
		||||
	"testing"
 | 
			
		||||
| 
						 | 
				
			
			@ -34,7 +36,7 @@ func TestServer_PublishAndPoll(t *testing.T) {
 | 
			
		|||
	require.Equal(t, "my first message", messages[0].Message)
 | 
			
		||||
	require.Equal(t, "my second\n\nmessage", messages[1].Message)
 | 
			
		||||
 | 
			
		||||
	response = request(t, s, "GET", "/mytopic/sse?poll=1", "", nil)
 | 
			
		||||
	response = request(t, s, "GET", "/mytopic/sse?poll=1&since=all", "", nil)
 | 
			
		||||
	lines := strings.Split(strings.TrimSpace(response.Body.String()), "\n")
 | 
			
		||||
	require.Equal(t, 3, len(lines))
 | 
			
		||||
	require.Equal(t, "my first message", toMessage(t, strings.TrimPrefix(lines[0], "data: ")).Message)
 | 
			
		||||
| 
						 | 
				
			
			@ -132,6 +134,9 @@ func TestServer_StaticSites(t *testing.T) {
 | 
			
		|||
	rr = request(t, s, "HEAD", "/", "", nil)
 | 
			
		||||
	require.Equal(t, 200, rr.Code)
 | 
			
		||||
 | 
			
		||||
	rr = request(t, s, "OPTIONS", "/", "", nil)
 | 
			
		||||
	require.Equal(t, 200, rr.Code)
 | 
			
		||||
 | 
			
		||||
	rr = request(t, s, "GET", "/does-not-exist.txt", "", nil)
 | 
			
		||||
	require.Equal(t, 404, rr.Code)
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -150,6 +155,10 @@ func TestServer_StaticSites(t *testing.T) {
 | 
			
		|||
	require.Equal(t, 200, rr.Code)
 | 
			
		||||
	require.Contains(t, rr.Body.String(), `Made with ❤️ by Philipp C. Heckel`)
 | 
			
		||||
	require.Contains(t, rr.Body.String(), `<script src=static/js/extra.js></script>`)
 | 
			
		||||
 | 
			
		||||
	rr = request(t, s, "GET", "/example.html", "", nil)
 | 
			
		||||
	require.Equal(t, 200, rr.Code)
 | 
			
		||||
	require.Contains(t, rr.Body.String(), "</html>")
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestServer_PublishLargeMessage(t *testing.T) {
 | 
			
		||||
| 
						 | 
				
			
			@ -168,6 +177,34 @@ func TestServer_PublishLargeMessage(t *testing.T) {
 | 
			
		|||
	require.Equal(t, truncated, messages[0].Message)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestServer_PublishPriority(t *testing.T) {
 | 
			
		||||
	s := newTestServer(t, newTestConfig(t))
 | 
			
		||||
 | 
			
		||||
	for prio := 1; prio <= 5; prio++ {
 | 
			
		||||
		response := request(t, s, "GET", fmt.Sprintf("/mytopic/publish?priority=%d", prio), fmt.Sprintf("priority %d", prio), nil)
 | 
			
		||||
		msg := toMessage(t, response.Body.String())
 | 
			
		||||
		require.Equal(t, prio, msg.Priority)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	response := request(t, s, "GET", "/mytopic/publish?priority=min", "test", nil)
 | 
			
		||||
	require.Equal(t, 1, toMessage(t, response.Body.String()).Priority)
 | 
			
		||||
 | 
			
		||||
	response = request(t, s, "GET", "/mytopic/send?priority=low", "test", nil)
 | 
			
		||||
	require.Equal(t, 2, toMessage(t, response.Body.String()).Priority)
 | 
			
		||||
 | 
			
		||||
	response = request(t, s, "GET", "/mytopic/send?priority=default", "test", nil)
 | 
			
		||||
	require.Equal(t, 3, toMessage(t, response.Body.String()).Priority)
 | 
			
		||||
 | 
			
		||||
	response = request(t, s, "GET", "/mytopic/send?priority=high", "test", nil)
 | 
			
		||||
	require.Equal(t, 4, toMessage(t, response.Body.String()).Priority)
 | 
			
		||||
 | 
			
		||||
	response = request(t, s, "GET", "/mytopic/send?priority=max", "test", nil)
 | 
			
		||||
	require.Equal(t, 5, toMessage(t, response.Body.String()).Priority)
 | 
			
		||||
 | 
			
		||||
	response = request(t, s, "GET", "/mytopic/trigger?priority=urgent", "test", nil)
 | 
			
		||||
	require.Equal(t, 5, toMessage(t, response.Body.String()).Priority)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestServer_PublishNoCache(t *testing.T) {
 | 
			
		||||
	s := newTestServer(t, newTestConfig(t))
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -182,6 +219,7 @@ func TestServer_PublishNoCache(t *testing.T) {
 | 
			
		|||
	messages := toMessages(t, response.Body.String())
 | 
			
		||||
	require.Empty(t, messages)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestServer_PublishAt(t *testing.T) {
 | 
			
		||||
	c := newTestConfig(t)
 | 
			
		||||
	c.MinDelay = time.Second
 | 
			
		||||
| 
						 | 
				
			
			@ -302,6 +340,59 @@ func TestServer_PublishWithNopCache(t *testing.T) {
 | 
			
		|||
	require.Empty(t, messages)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestServer_PublishAndPollSince(t *testing.T) {
 | 
			
		||||
	s := newTestServer(t, newTestConfig(t))
 | 
			
		||||
 | 
			
		||||
	request(t, s, "PUT", "/mytopic", "test 1", nil)
 | 
			
		||||
	time.Sleep(1100 * time.Millisecond)
 | 
			
		||||
 | 
			
		||||
	since := time.Now().Unix()
 | 
			
		||||
	request(t, s, "PUT", "/mytopic", "test 2", nil)
 | 
			
		||||
 | 
			
		||||
	response := request(t, s, "GET", fmt.Sprintf("/mytopic/json?poll=1&since=%d", since), "", nil)
 | 
			
		||||
	messages := toMessages(t, response.Body.String())
 | 
			
		||||
	require.Equal(t, 1, len(messages))
 | 
			
		||||
	require.Equal(t, "test 2", messages[0].Message)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestServer_PublishViaGET(t *testing.T) {
 | 
			
		||||
	s := newTestServer(t, newTestConfig(t))
 | 
			
		||||
 | 
			
		||||
	response := request(t, s, "GET", "/mytopic/trigger", "", nil)
 | 
			
		||||
	msg := toMessage(t, response.Body.String())
 | 
			
		||||
	require.NotEmpty(t, msg.ID)
 | 
			
		||||
	require.Equal(t, "triggered", msg.Message)
 | 
			
		||||
 | 
			
		||||
	response = request(t, s, "GET", "/mytopic/send?message=This+is+a+test&t=This+is+a+title&tags=skull&x-priority=5&delay=24h", "", nil)
 | 
			
		||||
	msg = toMessage(t, response.Body.String())
 | 
			
		||||
	require.NotEmpty(t, msg.ID)
 | 
			
		||||
	require.Equal(t, "This is a test", msg.Message)
 | 
			
		||||
	require.Equal(t, "This is a title", msg.Title)
 | 
			
		||||
	require.Equal(t, []string{"skull"}, msg.Tags)
 | 
			
		||||
	require.Equal(t, 5, msg.Priority)
 | 
			
		||||
	require.Greater(t, msg.Time, time.Now().Add(23*time.Hour).Unix())
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestServer_PublishFirebase(t *testing.T) {
 | 
			
		||||
	// This is unfortunately not much of a test, since it merely fires the messages towards Firebase,
 | 
			
		||||
	// but cannot re-read them. There is no way from Go to read the messages back, or even get an error back.
 | 
			
		||||
	// I tried everything. I already had written the test, and it increases the code coverage, so I'll leave it ... :shrug: ...
 | 
			
		||||
 | 
			
		||||
	c := newTestConfig(t)
 | 
			
		||||
	c.FirebaseKeyFile = firebaseServiceAccountFile(t) // May skip the test!
 | 
			
		||||
	s := newTestServer(t, c)
 | 
			
		||||
 | 
			
		||||
	// Normal message
 | 
			
		||||
	response := request(t, s, "PUT", "/mytopic", "This is a message for firebase", nil)
 | 
			
		||||
	msg := toMessage(t, response.Body.String())
 | 
			
		||||
	require.NotEmpty(t, msg.ID)
 | 
			
		||||
 | 
			
		||||
	// Keepalive message
 | 
			
		||||
	require.Nil(t, s.firebase(newKeepaliveMessage(firebaseControlTopic)))
 | 
			
		||||
 | 
			
		||||
	time.Sleep(500 * time.Millisecond) // Time for sends
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func newTestConfig(t *testing.T) *config.Config {
 | 
			
		||||
	conf := config.New(":80")
 | 
			
		||||
	conf.CacheFile = filepath.Join(t.TempDir(), "cache.db")
 | 
			
		||||
| 
						 | 
				
			
			@ -363,3 +454,15 @@ func toMessage(t *testing.T, s string) *message {
 | 
			
		|||
	require.Nil(t, json.NewDecoder(strings.NewReader(s)).Decode(&m))
 | 
			
		||||
	return &m
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func firebaseServiceAccountFile(t *testing.T) string {
 | 
			
		||||
	if os.Getenv("NTFY_TEST_FIREBASE_SERVICE_ACCOUNT_FILE") != "" {
 | 
			
		||||
		return os.Getenv("NTFY_TEST_FIREBASE_SERVICE_ACCOUNT_FILE")
 | 
			
		||||
	} else if os.Getenv("NTFY_TEST_FIREBASE_SERVICE_ACCOUNT") != "" {
 | 
			
		||||
		filename := filepath.Join(t.TempDir(), "firebase.json")
 | 
			
		||||
		require.NotNil(t, os.WriteFile(filename, []byte(os.Getenv("NTFY_TEST_FIREBASE_SERVICE_ACCOUNT")), 0600))
 | 
			
		||||
		return filename
 | 
			
		||||
	}
 | 
			
		||||
	t.SkipNow()
 | 
			
		||||
	return ""
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										2
									
								
								tools/fbsend/README.md
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										2
									
								
								tools/fbsend/README.md
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,2 @@
 | 
			
		|||
# fbsend
 | 
			
		||||
fbsend is a tiny tool to send data messages to Firebase. It's only used for testing.
 | 
			
		||||
							
								
								
									
										1
									
								
								util/embedfs/test.txt
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								util/embedfs/test.txt
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1 @@
 | 
			
		|||
This is a test file for embedfs_test.go
 | 
			
		||||
							
								
								
									
										44
									
								
								util/embedfs_test.go
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										44
									
								
								util/embedfs_test.go
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,44 @@
 | 
			
		|||
package util
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"embed"
 | 
			
		||||
	"github.com/stretchr/testify/require"
 | 
			
		||||
	"net/http"
 | 
			
		||||
	"net/http/httptest"
 | 
			
		||||
	"testing"
 | 
			
		||||
	"time"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
var (
 | 
			
		||||
	modTime = time.Now()
 | 
			
		||||
 | 
			
		||||
	//go:embed embedfs
 | 
			
		||||
	testFs       embed.FS
 | 
			
		||||
	testFsCached = &CachingEmbedFS{ModTime: modTime, FS: testFs}
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func TestCachingEmbedFS(t *testing.T) {
 | 
			
		||||
	s := http.FileServer(http.FS(testFsCached))
 | 
			
		||||
 | 
			
		||||
	rr := httptest.NewRecorder()
 | 
			
		||||
	req, _ := http.NewRequest("GET", "/embedfs/test.txt", nil)
 | 
			
		||||
	s.ServeHTTP(rr, req)
 | 
			
		||||
	require.Equal(t, 200, rr.Code)
 | 
			
		||||
	lastModified := rr.Header().Get("Last-Modified")
 | 
			
		||||
 | 
			
		||||
	rr = httptest.NewRecorder()
 | 
			
		||||
	req, _ = http.NewRequest("GET", "/embedfs/test.txt", nil)
 | 
			
		||||
	req.Header.Set("If-Modified-Since", lastModified)
 | 
			
		||||
	s.ServeHTTP(rr, req)
 | 
			
		||||
	require.Equal(t, 304, rr.Code) // Huzzah!
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestCachingEmbedFS_Range(t *testing.T) {
 | 
			
		||||
	s := http.FileServer(http.FS(testFsCached))
 | 
			
		||||
	rr := httptest.NewRecorder()
 | 
			
		||||
	req, _ := http.NewRequest("GET", "/embedfs/test.txt", nil)
 | 
			
		||||
	req.Header.Set("Range", "bytes=1-20")
 | 
			
		||||
	s.ServeHTTP(rr, req)
 | 
			
		||||
	require.Equal(t, 206, rr.Code)
 | 
			
		||||
	require.Equal(t, "his is a test file f", rr.Body.String())
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -58,8 +58,3 @@ func (l *Limiter) Value() int64 {
 | 
			
		|||
	defer l.mu.Unlock()
 | 
			
		||||
	return l.value
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Limit returns the defined limit
 | 
			
		||||
func (l *Limiter) Limit() int64 {
 | 
			
		||||
	return l.limit
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										30
									
								
								util/limit_test.go
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										30
									
								
								util/limit_test.go
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,30 @@
 | 
			
		|||
package util
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"testing"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func TestLimiter_Add(t *testing.T) {
 | 
			
		||||
	l := NewLimiter(10)
 | 
			
		||||
	if err := l.Add(5); err != nil {
 | 
			
		||||
		t.Fatal(err)
 | 
			
		||||
	}
 | 
			
		||||
	if err := l.Add(5); err != nil {
 | 
			
		||||
		t.Fatal(err)
 | 
			
		||||
	}
 | 
			
		||||
	if err := l.Add(5); err != ErrLimitReached {
 | 
			
		||||
		t.Fatalf("expected ErrLimitReached, got %#v", err)
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestLimiter_AddSub(t *testing.T) {
 | 
			
		||||
	l := NewLimiter(10)
 | 
			
		||||
	l.Add(5)
 | 
			
		||||
	if l.Value() != 5 {
 | 
			
		||||
		t.Fatalf("expected value to be %d, got %d", 5, l.Value())
 | 
			
		||||
	}
 | 
			
		||||
	l.Sub(2)
 | 
			
		||||
	if l.Value() != 3 {
 | 
			
		||||
		t.Fatalf("expected value to be %d, got %d", 3, l.Value())
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										56
									
								
								util/util_test.go
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										56
									
								
								util/util_test.go
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,56 @@
 | 
			
		|||
package util
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"github.com/stretchr/testify/require"
 | 
			
		||||
	"io/ioutil"
 | 
			
		||||
	"path/filepath"
 | 
			
		||||
	"testing"
 | 
			
		||||
	"time"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func TestDurationToHuman_SevenDays(t *testing.T) {
 | 
			
		||||
	d := 7 * 24 * time.Hour
 | 
			
		||||
	require.Equal(t, "7d", DurationToHuman(d))
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestDurationToHuman_MoreThanOneDay(t *testing.T) {
 | 
			
		||||
	d := 49 * time.Hour
 | 
			
		||||
	require.Equal(t, "2d1h", DurationToHuman(d))
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestDurationToHuman_LessThanOneDay(t *testing.T) {
 | 
			
		||||
	d := 17*time.Hour + 15*time.Minute
 | 
			
		||||
	require.Equal(t, "17h15m", DurationToHuman(d))
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestDurationToHuman_TenOfThings(t *testing.T) {
 | 
			
		||||
	d := 10*time.Hour + 10*time.Minute + 10*time.Second
 | 
			
		||||
	require.Equal(t, "10h10m10s", DurationToHuman(d))
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestDurationToHuman_Zero(t *testing.T) {
 | 
			
		||||
	require.Equal(t, "0", DurationToHuman(0))
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestRandomString(t *testing.T) {
 | 
			
		||||
	s1 := RandomString(10)
 | 
			
		||||
	s2 := RandomString(10)
 | 
			
		||||
	s3 := RandomString(12)
 | 
			
		||||
	require.Equal(t, 10, len(s1))
 | 
			
		||||
	require.Equal(t, 10, len(s2))
 | 
			
		||||
	require.Equal(t, 12, len(s3))
 | 
			
		||||
	require.NotEqual(t, s1, s2)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestFileExists(t *testing.T) {
 | 
			
		||||
	filename := filepath.Join(t.TempDir(), "somefile.txt")
 | 
			
		||||
	require.Nil(t, ioutil.WriteFile(filename, []byte{0x25, 0x86}, 0600))
 | 
			
		||||
	require.True(t, FileExists(filename))
 | 
			
		||||
	require.False(t, FileExists(filename+".doesnotexist"))
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestInStringList(t *testing.T) {
 | 
			
		||||
	s := []string{"one", "two"}
 | 
			
		||||
	require.True(t, InStringList(s, "two"))
 | 
			
		||||
	require.False(t, InStringList(s, "three"))
 | 
			
		||||
}
 | 
			
		||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue