diff --git a/new_relic_insights.info b/new_relic_insights.info index 6fd6e86..d306e08 100644 --- a/new_relic_insights.info +++ b/new_relic_insights.info @@ -19,3 +19,4 @@ files[] = test/src/InsightRemoteEntityQuery.test files[] = test/src/clients_connection_new_relic_insights_query.test files[] = test/src/Insight.test files[] = test/src/UI.test +files[] = test/src/insert.test diff --git a/new_relic_insights.install b/new_relic_insights.install index 794322a..d023ab8 100644 --- a/new_relic_insights.install +++ b/new_relic_insights.install @@ -29,6 +29,7 @@ function new_relic_insights_uninstall() { 'new_relic_insights_query_key', 'new_relic_insights_expiry_time', 'new_relic_insights_baseurl', + 'new_relic_insights_curl_timeout', 'new_relic_insights_ua', 'new_relic_insights_collect_accesslog', 'new_relic_insights_collect_watchdog', diff --git a/new_relic_insights.module b/new_relic_insights.module index 0afd58a..43b3648 100644 --- a/new_relic_insights.module +++ b/new_relic_insights.module @@ -329,7 +329,7 @@ function new_relic_insights_better_statistics_log($data) { $data['iid'] = new_relic_insights_generate_uuid(); // Add custom parameters to this transaction for each Better Stat field. - if (extension_loaded('newrelic')) { + if (function_exists('newrelic_add_custom_parameter')) { foreach ($data as $key => $value) { newrelic_add_custom_parameter($key, $value); } @@ -372,7 +372,7 @@ function new_relic_insights_post_event($data) { curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'POST'); curl_setopt($ch, CURLOPT_POSTFIELDS, $data); curl_setopt($ch, CURLOPT_RETURNTRANSFER, TRUE); - curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 5); + curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, variable_get('new_relic_insights_curl_timeout', 5)); curl_setopt($ch, CURLOPT_HTTPHEADER, array( 'Content-Type: application/json', 'Content-Length: ' . strlen($data), diff --git a/test/module/new_relic_insights_endpoint/new_relic_insights_endpoint.info b/test/module/new_relic_insights_endpoint/new_relic_insights_endpoint.info new file mode 100644 index 0000000..1255a7d --- /dev/null +++ b/test/module/new_relic_insights_endpoint/new_relic_insights_endpoint.info @@ -0,0 +1,4 @@ +name = New Relic Insights API Endpoint +description = Provides a mock API endpoint. FOR TESTING ONLY. Do not enable. +core = 7.x +hidden = TRUE diff --git a/test/module/new_relic_insights_endpoint/new_relic_insights_endpoint.install b/test/module/new_relic_insights_endpoint/new_relic_insights_endpoint.install new file mode 100644 index 0000000..91ab76f --- /dev/null +++ b/test/module/new_relic_insights_endpoint/new_relic_insights_endpoint.install @@ -0,0 +1,14 @@ + 'New Relic Insights Mock Insert API Endpoint', + 'page callback' => 'new_relic_insights_endpoint_callback', + 'page arguments' => array(2), + 'access callback' => TRUE, + 'type' => MENU_CALLBACK, + ); + + return $items; +} + +/** + * Menu callback for a mock "insert" API endpoint. + */ +function new_relic_insights_endpoint_callback($account_id) { + $data = variable_get('new_relic_insights_endpoint_data', array()); + + if (!isset($data[$account_id])) { + $data[$account_id] = array(); + } + + $data[$account_id][] = array( + 'post' => file_get_contents("php://input"), + 'headers' => new_relic_insights_endpoint_get_request_headers(), + ); + + variable_set('new_relic_insights_endpoint_data', $data); +} + +/** + * Returns relevant request headers. + * + * @return array + */ +function new_relic_insights_endpoint_get_request_headers() { + $headers = array(); + + if (isset($_SERVER['HTTP_X_INSERT_KEY'])) { + $headers['x-insert-key'] = $_SERVER['HTTP_X_INSERT_KEY']; + } + + if (isset($_SERVER['HTTP_X_QUERY_KEY'])) { + $headers['x-query-key'] = $_SERVER['HTTP_X_QUERY_KEY']; + } + + if (isset($_SERVER['HTTP_CONTENT_TYPE'])) { + $headers['content-type'] = $_SERVER['HTTP_CONTENT_TYPE']; + } + + return $headers; +} diff --git a/test/src/insert.test b/test/src/insert.test new file mode 100644 index 0000000..4f954fd --- /dev/null +++ b/test/src/insert.test @@ -0,0 +1,219 @@ + 'fooBar', + 'baz' => 'fizz', + 'biz' => 'buzz', + 'message' => 'This is a message.', + 'variables' => array(), + 'aid' => 1337, + ); + + protected $accountID = 1337; + + protected $insertKey = 'oMgwtFBbq1337='; + + public static function getInfo() { + return array( + 'name' => 'New Relic Insights: Insert Tests', + 'description' => 'Test expected Insert behaviours.', + 'group' => 'New Relic Insights', + ); + } + + public function setUp() { + parent::setUp(array( + 'new_relic_insights', + 'new_relic_insights_endpoint', + )); + + $endpoint = $this->getAbsoluteUrl('insights-insert'); + variable_set('new_relic_insights_baseurl', $endpoint); + variable_set('new_relic_insights_curl_timeout', 10); + } + + public function tearDown() { + parent::tearDown(); + variable_del('new_relic_insights_endpoint_data'); + } + + /** + * Sets up the user-agent string so that inward pointing http requests are + * picked up by the test install. + */ + protected function setUpInsightsUA() { + // Set our custom user-agent to ensure we hit the right DB/prefix/instance. + if (preg_match('/simpletest\d+/', $this->databasePrefix, $matches)) { + variable_set('new_relic_insights_ua', drupal_generate_test_ua($matches[0])); + } + } + + /** + * Sets up insert API credentials. + */ + protected function setUpCredentials() { + variable_set('new_relic_insights_account_id', $this->accountID); + variable_set('new_relic_insights_insert_key', $this->insertKey); + } + + /** + * Wrapper for getting variables that automatically refreshes variables. + * + * @param string $variable + * The name of the variable to return. + * + * @param mixed $default + * The default value to return if no value exists. + * + * @return mixed + * Returns the variable. + */ + protected function variableGet($variable, $default = NULL) { + $this->refreshVariables(); + return variable_get($variable, $default); + } + + /** + * Asserts that event insertions occurred as expected. + * + * @param int $account + * The account number for which events were expected to have been inserted. + * + * @param int $count + * The number of events that were exepcted to have been inserted. + */ + protected function assertValidInsert($account, $count = 1) { + $results = $this->variableGet('new_relic_insights_endpoint_data', array()); + + if ($count) { + $result = $results[$account]; + $this->assertIdentical(count($result), $count, 'Correct number of requests made.'); + foreach ($result as $event) { + $this->assertIdentical($event['headers']['x-insert-key'], $this->insertKey, 'Request included expected insert key.'); + $this->assertIdentical($event['headers']['content-type'], 'application/json', 'Request included correct content type.'); + } + } + else { + $this->assertIdentical($results, array(), 'No Insights events inserted.'); + } + } + + /** + * Tests the primary event post function. + */ + public function testPostEvent() { + // Ensure no POST is sent when no credentials are provided. + variable_del('new_relic_insights_account_id'); + $this->setUpInsightsUA(); + new_relic_insights_post_event($this->eventData); + $this->assertValidInsert($this->accountID, 0); + + // Ensure POST data is sent as expected when all credentials are provided. + $this->setUpCredentials(); + $this->setUpInsightsUA(); + new_relic_insights_post_event($this->eventData); + $this->assertValidInsert($this->accountID, 1); + $results = $this->variableGet('new_relic_insights_endpoint_data', array()); + $result = $results[$this->accountID][0]; + $this->assertIdentical($result['post'], drupal_json_encode($this->eventData), 'Request sent data as expected.'); + } + + /** + * Tests that watchdog event inserts work as expected. + */ + public function testWatchdogWorkflow() { + $this->setUpCredentials(); + + // Ensure no watchdog events are queued when not configured. + $queue = DrupalQueue::get(NEW_RELIC_INSIGHTS_QUEUE); + $queue->deleteQueue(); + variable_del('new_relic_insights_collect_watchdog'); + $this->setUpInsightsUA(); + new_relic_insights_watchdog($this->eventData); + $this->assertEqual($queue->numberOfItems(), 0, 'Watchdog event collection disabled by default.'); + + // Ensure multiple watchdog events can be queued. + variable_set('new_relic_insights_collect_watchdog', TRUE); + new_relic_insights_watchdog($this->eventData); + new_relic_insights_watchdog($this->eventData); + $this->assertEqual($queue->numberOfItems(), 2, 'Watchdog event collection created the correct number of queue items.'); + + // Running through the queue inserts the queued events. + $this->setUpInsightsUA(); + $ran = new_relic_insights_run_queue(); + $this->assertIdentical($ran, 2, 'Correct number of requests made.'); + $this->assertValidInsert($this->accountID); + + // Ensure the watchdog event includes expected values. + $results = $this->variableGet('new_relic_insights_endpoint_data', array()); + $results = $results[$this->accountID]; + $eventData = drupal_json_decode($results[0]['post']); + $this->assertEqual($eventData['eventType'], 'watchdog', 'Event includes type "watchdog" in payload.'); + $this->assertTrue(isset($eventData['iid']), 'Event payload includes an iid'); + $this->assertTrue(isset($eventData['fullMessage']), 'Event payload includes a "fullMessage" field.'); + } + + /** + * Tests that better stats will properly decorate transactions. + */ + public function testBetterStatsWorkflow() { + // Ensure the transaction is not decorated when not configured. + variable_del('new_relic_insights_collect_accesslog'); + new_relic_insights_better_statistics_log($this->eventData); + $data = newrelic_add_custom_parameter(); + $this->assertIdentical($data, array(), 'Transaction decoration disabled by default.'); + + // Ensure transaction decoration behaves as expected. + variable_set('new_relic_insights_collect_accesslog', TRUE); + new_relic_insights_better_statistics_log($this->eventData); + $data = newrelic_add_custom_parameter(); + $this->assertTrue(!isset($data['aid']), 'Transaction decoration removed the "aid" attribute.'); + $this->assertTrue(isset($data['iid']), 'Transaction decoration added an "iid" attribute.'); + $expected = $this->eventData; + unset($expected['aid']); + foreach ($expected as $key => $value) { + $this->assertIdentical($value, $data[$key], format_string('Successfully decorated transaction with %key = %value.', array( + '%key' => $key, + '%value' => (string) $data[$key], + ))); + } + } +} + +/** + * Mock implementation of the new relic transaction decoration mechanism. If + * called with no parameters, all results will be returned. + * + * @param string $name + * The key by which the given data should be stored. + * + * @param mixed $value + * The value to be stored. + * + * @param bool $reset + * TRUE if the statically stored values should be dumped and reset. + * + * @return array + * An associative array of data stored by callers. + */ +if (!function_exists('newrelic_add_custom_parameter')) { + function newrelic_add_custom_parameter($name = FALSE, $value = FALSE, $reset = FALSE) { + static $decorations; + if ($reset || empty($decorations)) { + $decorations = array(); + } + if ($name !== FALSE && $value !== FALSE) { + $decorations[$name] = $value; + } + return $decorations; + } +}