mirror of
https://github.com/esp8266/Arduino.git
synced 2025-07-30 16:24:09 +03:00
device tests: some of them can be run on host (#6912)
* device tests: mock scripts + rename some tests to enable mock-testing them * move symbol
This commit is contained in:
18
tests/device/test_sw_http_client/server.crt
Normal file
18
tests/device/test_sw_http_client/server.crt
Normal file
@ -0,0 +1,18 @@
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIIC3zCCAccCCQCUajf39FoF9jANBgkqhkiG9w0BAQsFADAWMRQwEgYDVQQDDAtj
|
||||
YS50bHMudGVzdDAeFw0xNjA2MTUwMjQ2MzFaFw0xNzA2MTUwMjQ2MzFaME0xCzAJ
|
||||
BgNVBAYTAkNOMREwDwYDVQQIDAhTaGFuZ2hhaTESMBAGA1UECgwJRXNwcmVzc2lm
|
||||
MRcwFQYDVQQDDA4xOTIuMTY4Ljc3LjIwODCCASIwDQYJKoZIhvcNAQEBBQADggEP
|
||||
ADCCAQoCggEBAMIchkjtd/6MxtKy+hcQ2msWIDMj1St4SIUx3yjiv3kWRUQxQIWL
|
||||
nc/TW5L1DCBdUk6BA/RQYTGya5D8wlN3Cwtc4J0y7HqDktxSgvMVXyVcLFg/GIzL
|
||||
8kFFf1KPUwB0EgOEnzgeFiY0QTF9hgqFiyucFc8V/lxneQ56jBOJ/whviJdbfJgf
|
||||
gAngNRSsFT04tcBrw87gqLMEhF9ydPCQfaz+XYse7fhXs4K+j5L1HJwHLuLrQMOn
|
||||
+nNC6B5NEp8lyYNY03Sq5SfHSMlkEdHL2sWxftOIb6Q3qcf+tgj/mkbNc3At+3hE
|
||||
NwVX+KYSV28Ll20+2tQlH10NHFpyYPJnX3ECAwEAATANBgkqhkiG9w0BAQsFAAOC
|
||||
AQEAMzNNwXnhp1OyNinGk700jRfe6zwdkpo1ZkclUD7fVEfnWxBj6j2lXReC6WT1
|
||||
isWXe/M9k+HS0fK7rTqDumeZYgp/Ui5LKgD2JTvLX91toG7apATWqLM1XPtLEGub
|
||||
webPO2CW/7aRfkPlXvP4Ss/QbqawxkmUKW3kJ4Lw1mmklu9ULGfiHPPUKvY5Qbe9
|
||||
9aDC/aTrjiaDmNoToZfAWuFBnxz95bKqFdbij35ZYzyVSNpezePtdOaDBR2mOMYd
|
||||
P54ENzFbOjVRm3K+7I9S+xa/lUPWnfjVJ026JDw/3/HVWvnwkZI8xNWVOk5CbdPH
|
||||
7d5Md13cmF1VQ0VNDqvqI3TZ5g==
|
||||
-----END CERTIFICATE-----
|
16
tests/device/test_sw_http_client/server.csr
Normal file
16
tests/device/test_sw_http_client/server.csr
Normal file
@ -0,0 +1,16 @@
|
||||
-----BEGIN CERTIFICATE REQUEST-----
|
||||
MIICgjCCAWoCAQAwPTEhMB8GA1UECgwYSW50ZXJuZXQgV2lkZ2l0cyBQdHkgTHRk
|
||||
MRgwFgYDVQQDDA9zZXJ2ZXIudGxzLnRlc3QwggEiMA0GCSqGSIb3DQEBAQUAA4IB
|
||||
DwAwggEKAoIBAQDCHIZI7Xf+jMbSsvoXENprFiAzI9UreEiFMd8o4r95FkVEMUCF
|
||||
i53P01uS9QwgXVJOgQP0UGExsmuQ/MJTdwsLXOCdMux6g5LcUoLzFV8lXCxYPxiM
|
||||
y/JBRX9Sj1MAdBIDhJ84HhYmNEExfYYKhYsrnBXPFf5cZ3kOeowTif8Ib4iXW3yY
|
||||
H4AJ4DUUrBU9OLXAa8PO4KizBIRfcnTwkH2s/l2LHu34V7OCvo+S9RycBy7i60DD
|
||||
p/pzQugeTRKfJcmDWNN0quUnx0jJZBHRy9rFsX7TiG+kN6nH/rYI/5pGzXNwLft4
|
||||
RDcFV/imEldvC5dtPtrUJR9dDRxacmDyZ19xAgMBAAGgADANBgkqhkiG9w0BAQsF
|
||||
AAOCAQEAE2xZjAkmVr7/p7LkV3UV3Y0wzeVP7kDBN7VnhNmEMw8xTKwXKsc6z5pY
|
||||
fa4/lmMMCIJ04dUPIgPC2qiYXJ1AXevLm0A7Blpay6HJilw51NMjhF6SLUkShl6e
|
||||
k3zyj9LnA5TxijsTrFy/km7qIoo6l7sR7+DwTOIlO/Sj/SDNJn+GAVL153zjxYCy
|
||||
eBYdEz07kxkeONLUjAW2bV+TkliqTdM6meRpf9GQGQksQrkHrudl7JyyKZTf2m6Z
|
||||
U33AnUp2SRIXESJmst824LKwkjLMYiXgxRK8ZSSP7iiNBj71DK1vQOZQwEZ2cCjW
|
||||
/pQxKJnHnRvZ3rityuShtHTUF+C39w==
|
||||
-----END CERTIFICATE REQUEST-----
|
27
tests/device/test_sw_http_client/server.key
Normal file
27
tests/device/test_sw_http_client/server.key
Normal file
@ -0,0 +1,27 @@
|
||||
-----BEGIN RSA PRIVATE KEY-----
|
||||
MIIEpAIBAAKCAQEAwhyGSO13/ozG0rL6FxDaaxYgMyPVK3hIhTHfKOK/eRZFRDFA
|
||||
hYudz9NbkvUMIF1SToED9FBhMbJrkPzCU3cLC1zgnTLseoOS3FKC8xVfJVwsWD8Y
|
||||
jMvyQUV/Uo9TAHQSA4SfOB4WJjRBMX2GCoWLK5wVzxX+XGd5DnqME4n/CG+Il1t8
|
||||
mB+ACeA1FKwVPTi1wGvDzuCoswSEX3J08JB9rP5dix7t+Fezgr6PkvUcnAcu4utA
|
||||
w6f6c0LoHk0SnyXJg1jTdKrlJ8dIyWQR0cvaxbF+04hvpDepx/62CP+aRs1zcC37
|
||||
eEQ3BVf4phJXbwuXbT7a1CUfXQ0cWnJg8mdfcQIDAQABAoIBAQCKfl2VFNEjbf/B
|
||||
fu8Om/iqpOuucSd7z2M2nSGSg02HsEsVX2qgnb+n8y4ICQxw3wSFfGl83Aiss5cp
|
||||
qB1h/bKEleywXA/7TXrv9XL2ys3K0xvUjpgOhMjApzwzhIz86tObbPlIybaV5XyK
|
||||
Ofvz79oe8EtjKFcGSNFHckoU+sValzH/gyaeIFuPFlJdsW9MOgN5lcyLDtddg2lr
|
||||
Z9k+OEEob9BC1dDmc5hxtnClfyiXW1ugjZovOBv79Mq2N8G/V8JZaTHxzEFdOGy3
|
||||
lUQ1B/s2xPDejQYGddc3N5+wF4ZuGHAYOmcv8IRaMvLdBMPmVnKdgDJeY4HheJY0
|
||||
z0t4BEYtAoGBAPL1exWZputT8GitBxTJcJB1crMzxvlSplS5mGxM36GgZxf2ql72
|
||||
8Ufa1hCAQLP7keKPWWl26Bt6Bm4uv/jsB/F8ezMloPvwwQ3nQrN8QY4ihtLLFcvK
|
||||
OMaErweMEVvQ6GdQHPGow5RqAkYj/X6jJjoAI88uV1yOx+TucmDneEnbAoGBAMyH
|
||||
04ZR3VNvzHSjDx2wo48FP7Cs/uCynOEd/bRb1I8n+fazIXIlWdWX2drFr2YRM+Uy
|
||||
UzAadgqFOu3Bvuk64UKwAlxX9jCIm1Ed5O1MUGPj+6OnW1oq/eYIbJ9RlFDyIEBv
|
||||
Ic+jV/zxojgpP38jpTzn2oC5jWWS+CjHpZNThtujAoGBAOquRB4xNRLLGcWCnPxv
|
||||
N9PSHuEKeoAKXbApNhomhz1P+0Uidp8UWSvXLj7yI422ysvVO8Crorgnvl5fuf9v
|
||||
vpx8aWSWTFIP0+riH4PP2mK45xJmKL+Yrg7Ty1225m2R9WsV719ebMzHOTsXOJ1C
|
||||
aoJL8EsHsEsvf9aanNENxRtRAoGAVIP34zQm2diDytqqX2FjZLENjWse8yi7bMag
|
||||
1ItxvSoOv2Nr+af3hCx4aE9x2CJZqGbwOxtkFZrSK/b4dZXQCWeDwjbS02FPlOhe
|
||||
dbQoL+7AR/La69qCCjEG+ZqTSBOVQirp9MwRisMqfjyFMXtAR3ejMbf69rMReoBt
|
||||
KgDE5DMCgYAkcBd7XyPjR6t09BXAcz8n5J8QCsoY4taNVeCmCCkl8rUt9y8tjQah
|
||||
5NtBk6i834p5/GbGhI4en/sisVUWzpOgc9G80lUAFQSR3weR6lOL1T+/cy691GqO
|
||||
wKAWbpOH01fJYmnbUjC7TC2idx033ZA9vJq/s9sNfG2CS2w3egcxag==
|
||||
-----END RSA PRIVATE KEY-----
|
252
tests/device/test_sw_http_client/test_sw_http_client.ino
Normal file
252
tests/device/test_sw_http_client/test_sw_http_client.ino
Normal file
@ -0,0 +1,252 @@
|
||||
#include <Arduino.h>
|
||||
#include <ESP8266WiFi.h>
|
||||
#include <ESP8266HTTPClient.h>
|
||||
#include <WiFiClientSecureAxTLS.h>
|
||||
#include <BSTest.h>
|
||||
#include <pgmspace.h>
|
||||
|
||||
BS_ENV_DECLARE();
|
||||
|
||||
void setup()
|
||||
{
|
||||
Serial.begin(115200);
|
||||
Serial.setDebugOutput(true);
|
||||
BS_RUN(Serial);
|
||||
}
|
||||
|
||||
bool pretest()
|
||||
{
|
||||
WiFi.persistent(false);
|
||||
WiFi.begin(getenv("STA_SSID"), getenv("STA_PASS"));
|
||||
while (WiFi.status() != WL_CONNECTED) {
|
||||
delay(500);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
const char* fp = "44 40 9E 34 92 2D E4 61 A4 89 A8 D5 7F 71 B7 62 B3 FD DD E1";
|
||||
|
||||
TEST_CASE("HTTP GET & POST requests", "[HTTPClient]")
|
||||
{
|
||||
{
|
||||
// small request
|
||||
WiFiClient client;
|
||||
HTTPClient http;
|
||||
http.begin(client, getenv("SERVER_IP"), 8088, "/");
|
||||
auto httpCode = http.GET();
|
||||
REQUIRE(httpCode == HTTP_CODE_OK);
|
||||
String payload = http.getString();
|
||||
REQUIRE(payload == "hello!!!");
|
||||
}
|
||||
{
|
||||
// request which returns 8000 bytes
|
||||
WiFiClient client;
|
||||
HTTPClient http;
|
||||
http.begin(client, getenv("SERVER_IP"), 8088, "/data?size=8000");
|
||||
auto httpCode = http.GET();
|
||||
REQUIRE(httpCode == HTTP_CODE_OK);
|
||||
String payload = http.getString();
|
||||
auto len = payload.length();
|
||||
REQUIRE(len == 8000);
|
||||
for (size_t i = 0; i < len; ++i) {
|
||||
if (payload[i] != 'a') {
|
||||
REQUIRE(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
{
|
||||
// can do two POST requests with one HTTPClient object (#1902)
|
||||
WiFiClient client;
|
||||
HTTPClient http;
|
||||
http.begin(client, getenv("SERVER_IP"), 8088, "/");
|
||||
http.addHeader("Content-Type", "text/plain");
|
||||
auto httpCode = http.POST("foo");
|
||||
Serial.println(httpCode);
|
||||
REQUIRE(httpCode == HTTP_CODE_OK);
|
||||
http.end();
|
||||
|
||||
httpCode = http.POST("bar");
|
||||
// its not expected to work but should not crash
|
||||
REQUIRE(httpCode == HTTPC_ERROR_CONNECTION_REFUSED);
|
||||
http.end();
|
||||
}
|
||||
{
|
||||
// 301 redirect with follow enabled
|
||||
WiFiClient client;
|
||||
HTTPClient http;
|
||||
http.setFollowRedirects(true);
|
||||
String uri = String("/redirect301?host=")+getenv("SERVER_IP");
|
||||
http.begin(client, getenv("SERVER_IP"), 8088, uri.c_str());
|
||||
auto httpCode = http.GET();
|
||||
REQUIRE(httpCode == HTTP_CODE_OK);
|
||||
String payload = http.getString();
|
||||
REQUIRE(payload == "redirect success");
|
||||
}
|
||||
{
|
||||
// 301 redirect with follow disabled
|
||||
WiFiClient client;
|
||||
HTTPClient http;
|
||||
String uri = String("/redirect301?host=")+getenv("SERVER_IP");
|
||||
http.begin(client, getenv("SERVER_IP"), 8088, uri.c_str());
|
||||
auto httpCode = http.GET();
|
||||
REQUIRE(httpCode == 301);
|
||||
}
|
||||
{
|
||||
// 302 redirect with follow enabled
|
||||
WiFiClient client;
|
||||
HTTPClient http;
|
||||
http.setFollowRedirects(true);
|
||||
String uri = String("/redirect302?host=")+getenv("SERVER_IP");
|
||||
http.begin(client, getenv("SERVER_IP"), 8088, uri.c_str());
|
||||
auto httpCode = http.GET();
|
||||
REQUIRE(httpCode == HTTP_CODE_OK);
|
||||
String payload = http.getString();
|
||||
REQUIRE(payload == "redirect success");
|
||||
}
|
||||
{
|
||||
// 302 redirect with follow disabled
|
||||
WiFiClient client;
|
||||
HTTPClient http;
|
||||
String uri = String("/redirect302?host=")+getenv("SERVER_IP");
|
||||
http.begin(client, getenv("SERVER_IP"), 8088, uri.c_str());
|
||||
auto httpCode = http.GET();
|
||||
REQUIRE(httpCode == 302);
|
||||
}
|
||||
{
|
||||
// 307 redirect with follow enabled
|
||||
WiFiClient client;
|
||||
HTTPClient http;
|
||||
http.setFollowRedirects(true);
|
||||
String uri = String("/redirect307?host=")+getenv("SERVER_IP");
|
||||
http.begin(client, getenv("SERVER_IP"), 8088, uri.c_str());
|
||||
auto httpCode = http.GET();
|
||||
REQUIRE(httpCode == HTTP_CODE_OK);
|
||||
String payload = http.getString();
|
||||
REQUIRE(payload == "redirect success");
|
||||
}
|
||||
{
|
||||
// 307 redirect with follow disabled
|
||||
WiFiClient client;
|
||||
HTTPClient http;
|
||||
String uri = String("/redirect307?host=")+getenv("SERVER_IP");
|
||||
http.begin(client, getenv("SERVER_IP"), 8088, uri.c_str());
|
||||
auto httpCode = http.GET();
|
||||
REQUIRE(httpCode == 307);
|
||||
}
|
||||
{
|
||||
// 301 exceeding redirect limit
|
||||
WiFiClient client;
|
||||
HTTPClient http;
|
||||
http.setFollowRedirects(true);
|
||||
http.setRedirectLimit(0);
|
||||
String uri = String("/redirect301?host=")+getenv("SERVER_IP");
|
||||
http.begin(client, getenv("SERVER_IP"), 8088, uri.c_str());
|
||||
auto httpCode = http.GET();
|
||||
REQUIRE(httpCode == 301);
|
||||
}
|
||||
{
|
||||
// POST 303 redirect with follow enabled
|
||||
WiFiClient client;
|
||||
HTTPClient http;
|
||||
http.setFollowRedirects(true);
|
||||
http.begin(client, getenv("SERVER_IP"), 8088, "/redirect303");
|
||||
auto httpCode = http.POST(getenv("SERVER_IP"));
|
||||
REQUIRE(httpCode == HTTP_CODE_OK);
|
||||
String payload = http.getString();
|
||||
REQUIRE(payload == "redirect success");
|
||||
}
|
||||
{
|
||||
// POST 303 redirect with follow disabled
|
||||
WiFiClient client;
|
||||
HTTPClient http;
|
||||
http.begin(client, getenv("SERVER_IP"), 8088, "/redirect303");
|
||||
auto httpCode = http.POST(getenv("SERVER_IP"));
|
||||
REQUIRE(httpCode == 303);
|
||||
}
|
||||
{
|
||||
// 302 redirect with follow disabled
|
||||
WiFiClient client;
|
||||
HTTPClient http;
|
||||
String uri = String("/redirect302?host=")+getenv("SERVER_IP");
|
||||
http.begin(client, getenv("SERVER_IP"), 8088, uri.c_str());
|
||||
auto httpCode = http.GET();
|
||||
REQUIRE(httpCode == 302);
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("HTTPS GET request", "[HTTPClient]")
|
||||
{
|
||||
//
|
||||
// Tests with BearSSL
|
||||
//
|
||||
{
|
||||
// small request
|
||||
BearSSL::WiFiClientSecure client;
|
||||
client.setFingerprint(fp);
|
||||
HTTPClient http;
|
||||
http.begin(client, getenv("SERVER_IP"), 8088, "/", fp);
|
||||
auto httpCode = http.GET();
|
||||
REQUIRE(httpCode == HTTP_CODE_OK);
|
||||
String payload = http.getString();
|
||||
REQUIRE(payload == "hello!!!");
|
||||
}
|
||||
{
|
||||
// request which returns 4000 bytes
|
||||
BearSSL::WiFiClientSecure client;
|
||||
client.setFingerprint(fp);
|
||||
HTTPClient http;
|
||||
http.begin(client, getenv("SERVER_IP"), 8088, "/data?size=4000", fp);
|
||||
auto httpCode = http.GET();
|
||||
REQUIRE(httpCode == HTTP_CODE_OK);
|
||||
String payload = http.getString();
|
||||
auto len = payload.length();
|
||||
REQUIRE(len == 4000);
|
||||
for (size_t i = 0; i < len; ++i) {
|
||||
if (payload[i] != 'a') {
|
||||
REQUIRE(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
//
|
||||
// Same tests with axTLS
|
||||
//
|
||||
#if !CORE_MOCK
|
||||
{
|
||||
// small request
|
||||
#pragma GCC diagnostic push
|
||||
#pragma GCC diagnostic ignored "-Wdeprecated-declarations"
|
||||
axTLS::WiFiClientSecure client;
|
||||
#pragma GCC diagnostic pop
|
||||
HTTPClient http;
|
||||
http.begin(client, getenv("SERVER_IP"), 8088, "/", fp);
|
||||
auto httpCode = http.GET();
|
||||
REQUIRE(httpCode == HTTP_CODE_OK);
|
||||
String payload = http.getString();
|
||||
REQUIRE(payload == "hello!!!");
|
||||
}
|
||||
{
|
||||
// request which returns 4000 bytes
|
||||
#pragma GCC diagnostic push
|
||||
#pragma GCC diagnostic ignored "-Wdeprecated-declarations"
|
||||
axTLS::WiFiClientSecure client;
|
||||
#pragma GCC diagnostic pop
|
||||
HTTPClient http;
|
||||
http.begin(client, getenv("SERVER_IP"), 8088, "/data?size=4000", fp);
|
||||
auto httpCode = http.GET();
|
||||
REQUIRE(httpCode == HTTP_CODE_OK);
|
||||
String payload = http.getString();
|
||||
auto len = payload.length();
|
||||
REQUIRE(len == 4000);
|
||||
for (size_t i = 0; i < len; ++i) {
|
||||
if (payload[i] != 'a') {
|
||||
REQUIRE(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
void loop()
|
||||
{
|
||||
}
|
||||
|
91
tests/device/test_sw_http_client/test_sw_http_client.py
Normal file
91
tests/device/test_sw_http_client/test_sw_http_client.py
Normal file
@ -0,0 +1,91 @@
|
||||
from mock_decorators import setup, teardown
|
||||
from flask import Flask, request, redirect
|
||||
from threading import Thread
|
||||
import urllib
|
||||
import os
|
||||
import ssl
|
||||
import time
|
||||
|
||||
@setup('HTTP GET & POST requests')
|
||||
def setup_http_get(e):
|
||||
app = Flask(__name__)
|
||||
def shutdown_server():
|
||||
func = request.environ.get('werkzeug.server.shutdown')
|
||||
if func is None:
|
||||
raise RuntimeError('Not running with the Werkzeug Server')
|
||||
func()
|
||||
@app.route('/shutdown')
|
||||
def shutdown():
|
||||
shutdown_server()
|
||||
return 'Server shutting down...'
|
||||
@app.route("/", methods = ['GET', 'POST'])
|
||||
def root():
|
||||
print('Got data: ' + request.data.decode());
|
||||
return 'hello!!!'
|
||||
@app.route("/data")
|
||||
def get_data():
|
||||
size = int(request.args['size'])
|
||||
return 'a'*size
|
||||
@app.route("/target")
|
||||
def target():
|
||||
return "redirect success"
|
||||
@app.route("/redirect301")
|
||||
def redirect301():
|
||||
return redirect("http://{}:8088/target".format(request.args['host']), code=301)
|
||||
@app.route("/redirect302")
|
||||
def redirect302():
|
||||
return redirect("http://{}:8088/target".format(request.args['host']), code=302)
|
||||
@app.route("/redirect303", methods = ['POST'])
|
||||
def redirect303():
|
||||
return redirect("http://{}:8088/target".format(request.data.decode()), code=303)
|
||||
@app.route("/redirect307")
|
||||
def redirect307():
|
||||
return redirect("http://{}:8088/target".format(request.args['host']), code=307)
|
||||
def flaskThread():
|
||||
app.run(host='0.0.0.0', port=8088)
|
||||
th = Thread(target=flaskThread)
|
||||
th.start()
|
||||
|
||||
@teardown('HTTP GET & POST requests')
|
||||
def teardown_http_get(e):
|
||||
response = urllib.request.urlopen('http://localhost:8088/shutdown')
|
||||
html = response.read()
|
||||
time.sleep(1) # avoid address in use error on macOS
|
||||
|
||||
|
||||
@setup('HTTPS GET request')
|
||||
def setup_http_get(e):
|
||||
app = Flask(__name__)
|
||||
def shutdown_server():
|
||||
func = request.environ.get('werkzeug.server.shutdown')
|
||||
if func is None:
|
||||
raise RuntimeError('Not running with the Werkzeug Server')
|
||||
func()
|
||||
@app.route('/shutdown')
|
||||
def shutdown():
|
||||
shutdown_server()
|
||||
return 'Server shutting down...'
|
||||
@app.route("/")
|
||||
def root():
|
||||
return 'hello!!!'
|
||||
@app.route("/data")
|
||||
def get_data():
|
||||
size = int(request.args['size'])
|
||||
return 'a'*size
|
||||
def flaskThread():
|
||||
p = os.path.dirname(os.path.abspath(__file__))
|
||||
context = (p + '/server.crt', p + '/server.key')
|
||||
print(context)
|
||||
app.run(host='0.0.0.0', port=8088, ssl_context=context)
|
||||
th = Thread(target=flaskThread)
|
||||
th.start()
|
||||
|
||||
@teardown('HTTPS GET request')
|
||||
def teardown_http_get(e):
|
||||
ctx = ssl.create_default_context()
|
||||
ctx.check_hostname = False
|
||||
ctx.verify_mode = ssl.CERT_NONE
|
||||
p = os.path.dirname(os.path.abspath(__file__))
|
||||
response = urllib.request.urlopen('https://localhost:8088/shutdown', context=ctx)
|
||||
html = response.read()
|
||||
|
Reference in New Issue
Block a user