mirror of
https://github.com/stylersnico/librenms.git
synced 2026-07-28 08:02:41 +02:00
Merge commit '4430658932762ead3661231492b8074f144ea4b6' into influxdb-php
Conflicts: lib/influxdb-php/.gitignore lib/influxdb-php/README.md
This commit is contained in:
@@ -10,7 +10,7 @@ use GuzzleHttp\Handler\MockHandler;
|
||||
use GuzzleHttp\HandlerStack;
|
||||
use InfluxDB\Client;
|
||||
use InfluxDB\Database;
|
||||
use InfluxDB\Driver\Guzzle;
|
||||
use InfluxDB\Driver\Guzzle as GuzzleDriver;
|
||||
use InfluxDB\ResultSet;
|
||||
use PHPUnit_Framework_MockObject_MockObject;
|
||||
use GuzzleHttp\Client as GuzzleClient;
|
||||
@@ -44,7 +44,7 @@ abstract class AbstractTest extends \PHPUnit_Framework_TestCase
|
||||
->disableOriginalConstructor()
|
||||
->getMock();
|
||||
|
||||
$this->resultData = file_get_contents(dirname(__FILE__) . '/result.example.json');
|
||||
$this->resultData = file_get_contents(dirname(__FILE__) . '/json/result.example.json');
|
||||
|
||||
$this->mockClient->expects($this->any())
|
||||
->method('getBaseURI')
|
||||
@@ -52,9 +52,17 @@ abstract class AbstractTest extends \PHPUnit_Framework_TestCase
|
||||
|
||||
$this->mockClient->expects($this->any())
|
||||
->method('query')
|
||||
->will($this->returnValue(new ResultSet($this->resultData)));
|
||||
->will($this->returnCallback(function ($db, $query) {
|
||||
Client::$lastQuery = $query;
|
||||
|
||||
$httpMockClient = new Guzzle($this->buildHttpMockClient(''));
|
||||
return new ResultSet($this->resultData);
|
||||
}));
|
||||
|
||||
$this->mockClient->expects($this->any())
|
||||
->method('write')
|
||||
->will($this->returnValue(true));
|
||||
|
||||
$httpMockClient = new GuzzleDriver($this->buildHttpMockClient(''));
|
||||
|
||||
// make sure the client has a valid driver
|
||||
$this->mockClient->expects($this->any())
|
||||
@@ -88,7 +96,13 @@ abstract class AbstractTest extends \PHPUnit_Framework_TestCase
|
||||
public function buildHttpMockClient($body)
|
||||
{
|
||||
// Create a mock and queue two responses.
|
||||
$mock = new MockHandler([new Response(200, array(), $body)]);
|
||||
$mock = new MockHandler([
|
||||
new Response(200, array(), $body),
|
||||
new Response(200, array(), $body),
|
||||
new Response(400, array(), 'fault{'),
|
||||
new Response(400, array(), $body),
|
||||
new Response(400, array(), $body),
|
||||
]);
|
||||
|
||||
$handler = HandlerStack::create($mock);
|
||||
return new GuzzleClient(['handler' => $handler]);
|
||||
@@ -114,7 +128,7 @@ abstract class AbstractTest extends \PHPUnit_Framework_TestCase
|
||||
->getMock();
|
||||
|
||||
if ($emptyResult) {
|
||||
$mockClient->expects($this->once())
|
||||
$mockClient->expects($this->any())
|
||||
->method('query')
|
||||
->will($this->returnValue(new ResultSet($this->getEmptyResult())));
|
||||
}
|
||||
|
||||
@@ -47,7 +47,7 @@ class AdminTest extends AbstractTest
|
||||
|
||||
public function testShowUsers()
|
||||
{
|
||||
$testJson = file_get_contents(dirname(__FILE__) . '/result-test-users.example.json');
|
||||
$testJson = file_get_contents(dirname(__FILE__) . '/json/result-test-users.example.json');
|
||||
|
||||
$clientMock = $this->getClientMock();
|
||||
$testResult = new ResultSet($testJson);
|
||||
|
||||
@@ -4,6 +4,7 @@ namespace InfluxDB\Test;
|
||||
|
||||
use InfluxDB\Client;
|
||||
use InfluxDB\Driver\Guzzle;
|
||||
use InfluxDB\Point;
|
||||
|
||||
class ClientTest extends AbstractTest
|
||||
{
|
||||
@@ -19,16 +20,26 @@ class ClientTest extends AbstractTest
|
||||
/** @var Client $client */
|
||||
protected $client = null;
|
||||
|
||||
public function testGetters()
|
||||
{
|
||||
$client = $this->getClient();
|
||||
|
||||
$this->assertEquals('http://localhost:8086', $client->getBaseURI());
|
||||
$this->assertInstanceOf('InfluxDB\Driver\Guzzle', $client->getDriver());
|
||||
$this->assertEquals('localhost', $client->getHost());
|
||||
$this->assertEquals('0', $client->getTimeout());
|
||||
}
|
||||
|
||||
public function testBaseURl()
|
||||
{
|
||||
$client = new Client('localhost', 8086);
|
||||
$client = $this->getClient();
|
||||
|
||||
$this->assertEquals($client->getBaseURI(), 'http://localhost:8086');
|
||||
}
|
||||
|
||||
public function testSelectDbShouldReturnDatabaseInstance()
|
||||
{
|
||||
$client = new Client('localhost', 8086);
|
||||
$client = $this->getClient();
|
||||
|
||||
$dbName = 'test-database';
|
||||
$database = $client->selectDB($dbName);
|
||||
@@ -38,23 +49,110 @@ class ClientTest extends AbstractTest
|
||||
$this->assertEquals($dbName, $database->getName());
|
||||
}
|
||||
|
||||
public function testSecureInstance()
|
||||
{
|
||||
$client = $this->getClient('test', 'test', true);
|
||||
$urlParts = parse_url($client->getBaseURI());
|
||||
|
||||
$this->assertEquals('https', $urlParts['scheme']);
|
||||
}
|
||||
|
||||
/**
|
||||
*/
|
||||
public function testGuzzleQuery()
|
||||
{
|
||||
$client = new Client('localhost', 8086);
|
||||
$client = $this->getClient('test', 'test');
|
||||
$query = "some-bad-query";
|
||||
|
||||
$bodyResponse = file_get_contents(dirname(__FILE__) . '/result.example.json');
|
||||
$bodyResponse = file_get_contents(dirname(__FILE__) . '/json/result.example.json');
|
||||
$httpMockClient = $this->buildHttpMockClient($bodyResponse);
|
||||
|
||||
$client->setDriver(new Guzzle($httpMockClient));
|
||||
|
||||
/** @var \InfluxDB\ResultSet $result */
|
||||
$result = $client->query(null, $query);
|
||||
$result = $client->query('somedb', $query);
|
||||
|
||||
$parameters = $client->getDriver()->getParameters();
|
||||
|
||||
$this->assertEquals(['test', 'test'], $parameters['auth']);
|
||||
$this->assertEquals('somedb', $parameters['database']);
|
||||
$this->assertInstanceOf('\InfluxDB\ResultSet', $result);
|
||||
|
||||
$this->assertEquals(
|
||||
true,
|
||||
$client->write(
|
||||
[
|
||||
'url' => 'http://localhost',
|
||||
'database' => 'influx_test_db',
|
||||
'method' => 'post'
|
||||
],
|
||||
[new Point('test', 1.0)]
|
||||
)
|
||||
);
|
||||
|
||||
$this->setExpectedException('\InvalidArgumentException');
|
||||
$client->query('test', 'bad-query');
|
||||
|
||||
$this->setExpectedException('\InfluxDB\Driver\Exception');
|
||||
$client->query('test', 'bad-query');
|
||||
}
|
||||
|
||||
public function testGetLastQuery()
|
||||
{
|
||||
$this->mockClient->query('test', 'SELECT * from test_metric');
|
||||
$this->assertEquals($this->getClient()->getLastQuery(), 'SELECT * from test_metric');
|
||||
}
|
||||
|
||||
public function testListDatabases()
|
||||
{
|
||||
$this->doTestResponse('databases.example.json', ['test', 'test1', 'test2'], 'listDatabases');
|
||||
}
|
||||
public function testListUsers()
|
||||
{
|
||||
$this->doTestResponse('users.example.json', ['user', 'admin'], 'listUsers');
|
||||
}
|
||||
|
||||
public function testFactoryMethod()
|
||||
{
|
||||
$client = $this->getClient('test', 'test', true);
|
||||
|
||||
$staticClient = \InfluxDB\Client::fromDSN('https+influxdb://test:test@localhost:8086/');
|
||||
|
||||
$this->assertEquals($client, $staticClient);
|
||||
|
||||
$db = $client->selectDB('testdb');
|
||||
$staticDB = \InfluxDB\Client::fromDSN('https+influxdb://test:test@localhost:8086/testdb');
|
||||
|
||||
$this->assertEquals($db, $staticDB);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $responseFile
|
||||
* @param array $result
|
||||
* @param string $method
|
||||
*/
|
||||
protected function doTestResponse($responseFile, array $result, $method)
|
||||
{
|
||||
$client = $this->getClient();
|
||||
$bodyResponse = file_get_contents(dirname(__FILE__) . '/json/'. $responseFile);
|
||||
$httpMockClient = $this->buildHttpMockClient($bodyResponse);
|
||||
|
||||
$client->setDriver(new Guzzle($httpMockClient));
|
||||
|
||||
$this->assertEquals($result, $client->$method());
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $username
|
||||
* @param string $password
|
||||
* @param bool|false $ssl
|
||||
*
|
||||
* @return Client
|
||||
*/
|
||||
protected function getClient($username = '', $password = '', $ssl = false)
|
||||
{
|
||||
return new Client('localhost', 8086, $username, $password, $ssl);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -27,34 +27,54 @@ class DatabaseTest extends AbstractTest
|
||||
{
|
||||
parent::setUp();
|
||||
|
||||
$this->resultData = file_get_contents(dirname(__FILE__) . '/result.example.json');
|
||||
$this->resultData = file_get_contents(dirname(__FILE__) . '/json/result.example.json');
|
||||
|
||||
$this->mockClient->expects($this->any())
|
||||
->method('listDatabases')
|
||||
->will($this->returnValue(array('test123', 'test')));
|
||||
|
||||
$this->dataToInsert = file_get_contents(dirname(__FILE__) . '/input.example.json');
|
||||
$this->dataToInsert = file_get_contents(dirname(__FILE__) . '/json/input.example.json');
|
||||
|
||||
}
|
||||
|
||||
public function testGetters()
|
||||
{
|
||||
$this->assertInstanceOf('InfluxDB\Client', $this->database->getClient());
|
||||
$this->assertInstanceOf('InfluxDB\Query\Builder', $this->database->getQueryBuilder());
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
public function testQuery()
|
||||
public function testQueries()
|
||||
{
|
||||
$testResultSet = new ResultSet($this->resultData);
|
||||
$this->assertEquals($this->database->query('SELECT * FROM test_metric'), $testResultSet);
|
||||
|
||||
$this->database->drop();
|
||||
$this->assertEquals('DROP DATABASE "influx_test_db"', Client::$lastQuery);
|
||||
|
||||
}
|
||||
|
||||
public function testCreateRetentionPolicy()
|
||||
|
||||
public function testRetentionPolicyQueries()
|
||||
{
|
||||
$retentionPolicy = new Database\RetentionPolicy('test', '1d', 1, true);
|
||||
$retentionPolicy = $this->getTestRetentionPolicy();
|
||||
|
||||
$mockClient = $this->getClientMock(true);
|
||||
$this->assertEquals(
|
||||
$this->getTestDatabase()->createRetentionPolicy($retentionPolicy),
|
||||
new ResultSet($this->getEmptyResult())
|
||||
);
|
||||
|
||||
$database = new Database('test', $mockClient);
|
||||
$this->database->listRetentionPolicies();
|
||||
$this->assertEquals('SHOW RETENTION POLICIES ON "influx_test_db"', Client::$lastQuery);
|
||||
|
||||
$this->assertEquals($database->createRetentionPolicy($retentionPolicy), new ResultSet($this->getEmptyResult()));
|
||||
$this->database->alterRetentionPolicy($this->getTestRetentionPolicy());
|
||||
$this->assertEquals(
|
||||
'ALTER RETENTION POLICY "test" ON "influx_test_db" DURATION 1d REPLICATION 1 DEFAULT',
|
||||
Client::$lastQuery
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -65,6 +85,38 @@ class DatabaseTest extends AbstractTest
|
||||
new Database(null, $this->mockClient);
|
||||
}
|
||||
|
||||
public function testCreate()
|
||||
{
|
||||
// test create with retention policy
|
||||
$this->database->create($this->getTestRetentionPolicy('influx_test_db'), true);
|
||||
$this->assertEquals(
|
||||
'CREATE RETENTION POLICY "influx_test_db" ON "influx_test_db" DURATION 1d REPLICATION 1 DEFAULT',
|
||||
Client::$lastQuery
|
||||
);
|
||||
|
||||
// test creating a database without create if not exists
|
||||
$this->database->create(null, true);
|
||||
$this->assertEquals('CREATE DATABASE IF NOT EXISTS "influx_test_db"', Client::$lastQuery);
|
||||
|
||||
// test creating a database without create if not exists
|
||||
$this->database->create(null, false);
|
||||
$this->assertEquals('CREATE DATABASE "influx_test_db"', Client::$lastQuery);
|
||||
|
||||
|
||||
$this->mockClient->expects($this->any())
|
||||
->method('query')
|
||||
->will($this->returnCallback(function () {
|
||||
throw new \Exception('test exception');
|
||||
}));
|
||||
|
||||
|
||||
// test an exception being handled correctly
|
||||
$this->setExpectedException('\InfluxDB\Database\Exception');
|
||||
$this->database->create($this->getTestRetentionPolicy('influx_test_db'), false);
|
||||
|
||||
}
|
||||
|
||||
|
||||
public function testExists()
|
||||
{
|
||||
$database = new Database('test', $this->mockClient);
|
||||
@@ -96,5 +148,76 @@ class DatabaseTest extends AbstractTest
|
||||
);
|
||||
|
||||
$this->assertEquals(true, $this->database->writePoints(array($point1, $point2)));
|
||||
|
||||
$this->mockClient->expects($this->once())
|
||||
->method('write')
|
||||
->will($this->throwException(new \Exception('Test exception')));
|
||||
|
||||
$this->setExpectedException('InfluxDB\Exception');
|
||||
|
||||
$this->database->writePoints(array($point1, $point2));
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
public function testQueryBuilderOrderBy()
|
||||
{
|
||||
$this->assertEquals(
|
||||
$this->database->getQueryBuilder()
|
||||
->from('test_metric')
|
||||
->orderBy('time', 'DESC')->getQuery(),
|
||||
'SELECT * FROM "test_metric" ORDER BY time DESC');
|
||||
|
||||
$this->assertEquals(
|
||||
$this->database->getQueryBuilder()
|
||||
->from('test_metric')
|
||||
->orderBy('time', 'DESC')
|
||||
->orderBy('some_field', 'ASC')
|
||||
->getQuery(),
|
||||
'SELECT * FROM "test_metric" ORDER BY time DESC,some_field ASC');
|
||||
}
|
||||
|
||||
/**
|
||||
* @see https://github.com/influxdata/influxdb-php/pull/36
|
||||
*/
|
||||
public function testReservedNames()
|
||||
{
|
||||
$database = new Database('stats', $this->mockClient);
|
||||
|
||||
// test handling of reserved keywords in database name
|
||||
$database->create();
|
||||
$this->assertEquals('CREATE DATABASE IF NOT EXISTS "stats"', Client::$lastQuery);
|
||||
|
||||
$database->listRetentionPolicies();
|
||||
$this->assertEquals('SHOW RETENTION POLICIES ON "stats"', Client::$lastQuery);
|
||||
|
||||
// test handling of reserved keywords in retention policy names
|
||||
$database->create($this->getTestRetentionPolicy('default'));
|
||||
$this->assertEquals(
|
||||
'CREATE RETENTION POLICY "default" ON "stats" DURATION 1d REPLICATION 1 DEFAULT',
|
||||
Client::$lastQuery
|
||||
);
|
||||
|
||||
// test handling of reserved keywords in measurement names
|
||||
$this->assertEquals($database->getQueryBuilder()->from('server')->getQuery(), 'SELECT * FROM "server"');
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $name
|
||||
*
|
||||
* @return Database
|
||||
*/
|
||||
protected function getTestDatabase($name = 'test')
|
||||
{
|
||||
return new Database($name, $this->getClientMock(true));
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $name
|
||||
*
|
||||
* @return Database\RetentionPolicy
|
||||
*/
|
||||
protected function getTestRetentionPolicy($name = 'test')
|
||||
{
|
||||
return new Database\RetentionPolicy($name, '1d', 1, true);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,10 +1,4 @@
|
||||
<?php
|
||||
/**
|
||||
* Created by PhpStorm.
|
||||
* User: dmartinez
|
||||
* Date: 18-6-15
|
||||
* Time: 17:39
|
||||
*/
|
||||
|
||||
namespace InfluxDB\Test;
|
||||
|
||||
@@ -15,16 +9,119 @@ class PointTest extends \PHPUnit_Framework_TestCase
|
||||
{
|
||||
public function testPointStringRepresentation()
|
||||
{
|
||||
$expected = 'cpu_load_short,host=server01,region=us-west cpucount=10,value=0.64 1435222310';
|
||||
$expected = 'instance,host=server01,region=us-west cpucount=10i,free=1i,test="string",bool=false,value=1.11 1440494531376778481';
|
||||
|
||||
$point = new Point(
|
||||
'cpu_load_short',
|
||||
0.64,
|
||||
array('host' => 'server01', 'region' => 'us-west'),
|
||||
array('cpucount' => 10),
|
||||
1435222310
|
||||
);
|
||||
$point = $this->getPoint('1440494531376778481');
|
||||
|
||||
$this->assertEquals($expected, (string) $point);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the Point class throw an exception when invalid timestamp are given.
|
||||
*
|
||||
* @dataProvider wrongTimestampProvider
|
||||
* @expectedException \InfluxDB\Database\Exception
|
||||
*/
|
||||
public function testPointWrongTimestamp($timestamp)
|
||||
{
|
||||
$this->getPoint($timestamp);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the Point class accept all valid timestamp given.
|
||||
*
|
||||
* @dataProvider validTimestampProvider
|
||||
*/
|
||||
public function testPointValidTimestamp($timestamp)
|
||||
{
|
||||
$expected = 'instance,host=server01,region=us-west cpucount=10i,free=1i,test="string",bool=false,value=1.11' . (($timestamp) ? ' ' . $timestamp : '');
|
||||
|
||||
$point = $this->getPoint($timestamp);
|
||||
|
||||
$this->assertEquals($expected, (string) $point);
|
||||
}
|
||||
|
||||
public function testGettersAndSetters()
|
||||
{
|
||||
$timestamp = time();
|
||||
$timestamp2 = time() + 3600;
|
||||
$point = $this->getPoint($timestamp);
|
||||
|
||||
$this->assertEquals($timestamp, $point->getTimestamp());
|
||||
$point->setTimestamp($timestamp2);
|
||||
$this->assertEquals($timestamp2, $point->getTimestamp());
|
||||
|
||||
$this->assertEquals('instance', $point->getMeasurement());
|
||||
$point->setMeasurement('test');
|
||||
$this->assertEquals('test', $point->getMeasurement());
|
||||
|
||||
$fields = $point->getFields();
|
||||
$this->assertEquals(1.11, $fields['value']);
|
||||
$this->assertEquals([
|
||||
'cpucount' => '10i',
|
||||
'free' => '1i',
|
||||
'test' => "\"string\"",
|
||||
'bool' => 'false',
|
||||
'value' => '1.1100000000000001'
|
||||
], $fields);
|
||||
|
||||
$point->setFields(['cpucount' => 11]);
|
||||
$this->assertEquals(['cpucount' => '11i'], $point->getFields());
|
||||
|
||||
$this->assertEquals(['host' => 'server01', 'region' => 'us-west'], $point->getTags());
|
||||
$point->setTags(['test' => 'value']);
|
||||
$this->assertEquals(['test' => 'value'], $point->getTags());
|
||||
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Provide wrong timestamp value for testing.
|
||||
*/
|
||||
public function wrongTimestampProvider()
|
||||
{
|
||||
return [
|
||||
['2015-10-27 14:17:40'],
|
||||
['INVALID'],
|
||||
['aa778481'],
|
||||
['1477aee'],
|
||||
['15.258'],
|
||||
['15,258'],
|
||||
[15.258],
|
||||
[true]
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Provide valid timestamp value for testing.
|
||||
*/
|
||||
public function validTimestampProvider()
|
||||
{
|
||||
return [
|
||||
[time()], // Current time returned by the PHP time function.
|
||||
[0], // Day 0
|
||||
[~PHP_INT_MAX], // Minimum value integer
|
||||
[PHP_INT_MAX], // Maximum value integer
|
||||
['1440494531376778481'] // Text timestamp
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an instance of \InfluxDB\Point
|
||||
*
|
||||
* @param int $timestamp
|
||||
*
|
||||
* @return Point
|
||||
*/
|
||||
private function getPoint($timestamp)
|
||||
{
|
||||
return new Point(
|
||||
'instance', // the name of the measurement
|
||||
1.11, // measurement value
|
||||
['host' => 'server01', 'region' => 'us-west'],
|
||||
['cpucount' => 10, 'free' => 1, 'test' => "string", 'bool' => false],
|
||||
$timestamp
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -10,7 +10,7 @@ class ResultSetTest extends \PHPUnit_Framework_TestCase
|
||||
|
||||
public function setUp()
|
||||
{
|
||||
$resultJsonExample = file_get_contents(dirname(__FILE__) . '/result.example.json');
|
||||
$resultJsonExample = file_get_contents(dirname(__FILE__) . '/json/result.example.json');
|
||||
$this->resultSet = new ResultSet($resultJsonExample);
|
||||
}
|
||||
|
||||
@@ -65,7 +65,7 @@ EOD;
|
||||
*/
|
||||
public function testGetPointsFromNameWithoudTags()
|
||||
{
|
||||
$resultJsonExample = file_get_contents(dirname(__FILE__) . '/result-no-tags.example.json');
|
||||
$resultJsonExample = file_get_contents(dirname(__FILE__) . '/json/result-no-tags.example.json');
|
||||
$this->resultSet = new ResultSet($resultJsonExample);
|
||||
|
||||
$measurementName = 'cpu_load_short';
|
||||
@@ -95,6 +95,11 @@ EOD;
|
||||
|
||||
}
|
||||
|
||||
public function testGetSeries()
|
||||
{
|
||||
$this->assertEquals(['time', 'value'], $this->resultSet->getColumns());
|
||||
}
|
||||
|
||||
/**
|
||||
* We can get points from measurement
|
||||
*/
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
{
|
||||
"results":[
|
||||
{
|
||||
"series":[
|
||||
{
|
||||
"name":"databases",
|
||||
"columns":[
|
||||
"name"
|
||||
],
|
||||
"values":[
|
||||
[
|
||||
"test"
|
||||
],
|
||||
[
|
||||
"test1"
|
||||
],
|
||||
[
|
||||
"test2"
|
||||
]
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"results":[
|
||||
{
|
||||
"series":[
|
||||
{
|
||||
"columns":[
|
||||
"user",
|
||||
"admin"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
Reference in New Issue
Block a user