From e6eb32c57631e54193a2f8b42b2a6ad5964048f2 Mon Sep 17 00:00:00 2001 From: Ryan Smith <0ryansmith1994@gmail.com> Date: Wed, 15 Apr 2015 10:54:28 +0100 Subject: [PATCH 01/34] Corrects syntax error. --- app/locker/helpers/Helpers.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/app/locker/helpers/Helpers.php b/app/locker/helpers/Helpers.php index c7e4203eae..6d8f22316f 100644 --- a/app/locker/helpers/Helpers.php +++ b/app/locker/helpers/Helpers.php @@ -127,10 +127,10 @@ static function getEnvVar($var) { $defaults = include base_path() . '/.env.php'; $value = $defaults[$var]; } - + return $value; } - + /** * Determines which identifier is currently in use in the given actor. * @param \stdClass $actor. @@ -154,7 +154,7 @@ static function validateAtom(XAPIAtom $atom, $trace = null) { if (count($errors) > 0) { throw new Exceptions\Validation(array_map(function (XAPIError $error) use ($trace) { return (string) ($trace === null ? $error : $error->addTrace($trace)); - }, $errors))); + }, $errors)); } } } From 82cdd0ca4be16d59a99a655cf75767d1fc8814e8 Mon Sep 17 00:00:00 2001 From: Ryan Smith <0ryansmith1994@gmail.com> Date: Wed, 15 Apr 2015 10:55:32 +0100 Subject: [PATCH 02/34] Corrects syntax error. --- app/locker/repository/Statement/IndexOptions.php | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/app/locker/repository/Statement/IndexOptions.php b/app/locker/repository/Statement/IndexOptions.php index 78194ed6cb..f8a40baddb 100644 --- a/app/locker/repository/Statement/IndexOptions.php +++ b/app/locker/repository/Statement/IndexOptions.php @@ -21,10 +21,10 @@ private function validate() { $opts = $this->options; if ($opts['offset'] < 0) throw new Exceptions\Exception('`offset` must be a positive interger.'); if ($opts['limit'] < 1) throw new Exceptions\Exception('`limit` must be a positive interger.'); - XApiHelpers::checkType('related_activities', 'boolean', $opts['related_agents'])); - XApiHelpers::checkType('related_activities', 'boolean', $opts['related_activities'])); - XApiHelpers::checkType('attachments', 'boolean', $opts['attachments'])); - XApiHelpers::checkType('ascending', 'boolean', $opts['ascending'])); + XApiHelpers::checkType('related_activities', 'boolean', $opts['related_agents']); + XApiHelpers::checkType('related_activities', 'boolean', $opts['related_activities']); + XApiHelpers::checkType('attachments', 'boolean', $opts['attachments']); + XApiHelpers::checkType('ascending', 'boolean', $opts['ascending']); } /** From 0f38c6dfa6b666b5dab3f8ed26b3f076eb159e10 Mon Sep 17 00:00:00 2001 From: Ryan Smith <0ryansmith1994@gmail.com> Date: Wed, 15 Apr 2015 13:58:19 +0100 Subject: [PATCH 03/34] Creates statement indexer. --- .../repository/Statement/EloquentIndexer.php | 168 ++++++++++++++++-- 1 file changed, 150 insertions(+), 18 deletions(-) diff --git a/app/locker/repository/Statement/EloquentIndexer.php b/app/locker/repository/Statement/EloquentIndexer.php index c97d1d33fc..465556f3d2 100644 --- a/app/locker/repository/Statement/EloquentIndexer.php +++ b/app/locker/repository/Statement/EloquentIndexer.php @@ -1,68 +1,200 @@ Mixed] $opts + * @param IndexOptions $opts * @return [Model] */ - public function index(array $opts) { - $opts = new IndexOptions($opts); + public function index(IndexOptions $opts) { return $this->where($opts); } + /** + * Constructs a query restricted by the given options. + * @param IndexOptions $opts + * @return Builder + */ protected function where(IndexOptions $opts) { - $builder = $this->where($opts->options); + $builder = parent::where($opts->options); return $this->constructFilterOpts($builder, $opts, [ - 'agent' => function ($agent, IndexOptions $opts) { - Helpers::validateAtom(\Locker\XApi\Agent::createFromJSON($agent)); - return $this->matchAgent($agent, $options); + 'agent' => function ($value, $builder, IndexOptions $opts) { + return $this->matchAgent($value, $builder, $opts); + }, + 'activity' => function ($value, $builder, IndexOptions $opts) { + return $this->matchActivity($value, $builder, $opts); + }, + 'verb' => function ($value, $builder, IndexOptions $opts) { + return $this->addWhere($builder, 'statement.verb.id', $value); + }, + 'registration' => function ($value, $builder, IndexOptions $opts) { + return $this->addWhere($builder, 'statement.context.registration', $value); + }, + 'since' => function ($value, $builder, IndexOptions $opts) { + return $this->addWhere($builder, 'statement.timestamp', $value); + }, + 'until' => function ($value, $builder, IndexOptions $opts) { + return $this->addWhere($builder, 'statement.timestamp', $value); + }, + 'active' => function ($value, $builder, IndexOptions $opts) { + return $this->addWhere($builder, 'active', $value); + }, + 'voided' => function ($value, $builder, IndexOptions $opts) { + return $this->addWhere($builder, 'voided', $value); } ]); } + /** + * Adds where to builder. + * @param Builder $builder + * @param String $key + * @param Mixed $value + * @return Builder + */ + private function addWhere(Builder $builder, $key, $value) { + return $builder->where(function ($query) use ($key, $value) { + return $query + ->orWhere($key, $value) + ->orWhere('refs.'.$key, $value); + }); + } + + /** + * Adds wheres to builder. + * @param Builder $builder + * @param [String] $keys + * @param Mixed $value + * @return Builder + */ + private function addWheres(Builder $builder, array $keys, $value) { + return $builder->where(function ($query) use ($keys, $value) { + foreach ($keys as $key) { + $query = $this->addWhere($query, $key, $value); + } + return $query; + }); + } + + /** + * Extends a given Builder using the given options and option builders. + * @param Builder $builder + * @param IndexOptions $opts. + * @param [String => Callable] $builders Option builders. + * @return Builder + */ + private function constructFilterOpts(Builder $builder, IndexOptions $opts, array $builders) { + foreach ($builders as $opt => $opt_builder) { + $opt_value = $opts->getOpt($opt); + $builder = $opt_value === null ? $builder : $opt_builder($opt_value, $opt_builder, $opts); + } + return $builder; + } + /** * Formats statements. * @param Builder $builder - * @param [String => Mixed] $opts + * @param IndexOptions $opts * @return [Model] Formatted statements. */ - public function format(Builder $builder, array $opts) { - if ($opts['format'] === 'exact') { + public function format(Builder $builder, IndexOptions $opts) { + // Determines the formatter to be used. + $format = $opts->getOpt('format'); + if ($format === 'exact') { $formatter = function ($model, $opts) { return $model; }; - } else if ($opts['format'] === 'ids') { + } else if ($format === 'ids') { $formatter = function ($model, $opts) { return $this->formatter->identityStatement($model); }; - } else if ($opts['format'] === 'canonical') { + } else if ($format === 'canonical') { $formatter = function ($model, $opts) { return $this->formatter->canonicalStatement($model, $opts['langs']); }; } else { - throw new Exceptions\Exception("`$opts['format']` is not a valid format."); + throw new Exceptions\Exception("`$format` is not a valid format."); } - return $builder->get()->each(function (Model $model) use ($opts) { - return $formatter($model, $opts); + // Returns the models. + return $builder + ->offset($opts->getOpt('offset')) + ->limit($opts->getOpt('limit')) + ->get() + ->each(function (Model $model) use ($opts) { + return $formatter($model, $opts); + }); + } + + /** + * Constructs a Mongo match using the given agent and options. + * @param String $agent Agent to be matched. + * @param IndexOptions $options Index options. + * @return Builder + */ + private function matchAgent($agent, Builder $builder, IndexOptions $options) { + $id_key = Helpers::getAgentIdentifier($agent); + $id_val = $agent->{$id_key}; + + return $builder->where(function ($query) use ($id_val, $opts) { + $keys = ["statement.actor.$id_key", "statement.object.$id_key"]; + + if ($opts->getOpt('related_agents') === true) { + $keys = array_merge($keys, [ + "statement.authority.$id_key", + "statement.context.instructor.$id_key", + "statement.context.team.$id_key" + ]); + } + + $query = $this->addWheres($keys); + }); + } + + /** + * Constructs a Mongo match using the given activity and options. + * @param String $activity Activity to be matched. + * @param IndexOptions $options Index options. + * @return Builder + */ + private function matchActivity($activity, Builder $builder, IndexOptions $options) { + return $builder->where(function ($query) use ($activity, $opts) { + $keys = ['statement.object.id']; + + if ($opts->getOpt('related_activities') === true) { + $keys = array_merge($keys, [ + 'statement.context.contextActivities.parent.id', + 'statement.context.contextActivities.grouping.id', + 'statement.context.contextActivities.category.id', + 'statement.context.contextActivities.other.id' + ]); + } + + $query = $this->addWheres($keys); }); } /** * Counts statements. * @param Builder $builder - * @param [String => Mixed] $opts + * @param IndexOptions $opts * @return Int Number of statements in Builder. */ - public function count(Builder $builder, array $opts) { + public function count(Builder $builder, IndexOptions $opts) { return $builder->count(); } } From 3da5ddcb7d4e9ae6d8c65c5ba28ac7ac2d2c143a Mon Sep 17 00:00:00 2001 From: Ryan Smith <0ryansmith1994@gmail.com> Date: Wed, 15 Apr 2015 13:58:59 +0100 Subject: [PATCH 04/34] Creates statement shower. --- .../repository/Statement/EloquentShower.php | 55 +++++++++++++++++++ 1 file changed, 55 insertions(+) create mode 100644 app/locker/repository/Statement/EloquentShower.php diff --git a/app/locker/repository/Statement/EloquentShower.php b/app/locker/repository/Statement/EloquentShower.php new file mode 100644 index 0000000000..40784291ea --- /dev/null +++ b/app/locker/repository/Statement/EloquentShower.php @@ -0,0 +1,55 @@ + Mixed] $opts + * @return Model + */ + public function show($id, array $opts) { + $opts = $this->mergeDefaultOptions($opts); + $model = $this->query->where($opts) + ->where('statement.id', $id) + ->where('voided', $opts['voided']) + ->where('active', $opts['active']) + ->first(); + + if ($model === null) throw new Exceptions\NotFound($id, $this->model); + + return $model; + } + + /** + * Formats the model before returning. + * @param Model $model + * @return Model + */ + public function format(Model $model) { + return $model->statement; + } + + /** + * Returns all of the index options set to their default or given value (using the given options). + * @param [String => mixed] $opts + * @return [String => mixed] + */ + protected function mergeDefaultOptions(array $opts) { + return array_merge([ + 'voided' => false, + 'active' => true + ], $opts); + } +} From 3cba7fa2a84096f7fc77aecda1ecd31ccc16c741 Mon Sep 17 00:00:00 2001 From: Ryan Smith <0ryansmith1994@gmail.com> Date: Wed, 15 Apr 2015 13:59:33 +0100 Subject: [PATCH 05/34] Creates class for IndexOptions. --- .../repository/Statement/IndexOptions.php | 37 +++++++++++++++++-- 1 file changed, 33 insertions(+), 4 deletions(-) diff --git a/app/locker/repository/Statement/IndexOptions.php b/app/locker/repository/Statement/IndexOptions.php index f8a40baddb..11877f3919 100644 --- a/app/locker/repository/Statement/IndexOptions.php +++ b/app/locker/repository/Statement/IndexOptions.php @@ -1,6 +1,7 @@ options; + + // Validates types. + $this->validateOpt($opts, 'agent', 'Agent'); + $this->validateOpt($opts, 'activity', 'IRI'); + $this->validateOpt($opts, 'verb', 'IRI'); + $this->validateOpt($opts, 'registration', 'UUID'); + $this->validateOpt($opts, 'since', 'Timestamp'); + $this->validateOpt($opts, 'until', 'Timestamp'); + $this->validateOpt($opts, 'active', 'Boolean'); + $this->validateOpt($opts, 'voided', 'Boolean'); + $this->validateOpt($opts, 'related_activities', 'Boolean'); + $this->validateOpt($opts, 'related_agents', 'Boolean'); + $this->validateOpt($opts, 'ascending', 'Boolean'); + $this->validateOpt($opts, 'format', 'String'); + $this->validateOpt($opts, 'offset', 'Integer'); + $this->validateOpt($opts, 'limit', 'Integer'); + $this->validateOpt($opts, 'langs', 'Collection'); + $this->validateOpt($opts, 'attachments', 'Boolean'); + $this->validateOpt($opts, 'lrs_id', 'String'); + + // Validates values. + if (!isset($opts['lrs_id'])) throw new Exceptions\Exception('`lrs_id` must be set.'); if ($opts['offset'] < 0) throw new Exceptions\Exception('`offset` must be a positive interger.'); if ($opts['limit'] < 1) throw new Exceptions\Exception('`limit` must be a positive interger.'); - XApiHelpers::checkType('related_activities', 'boolean', $opts['related_agents']); - XApiHelpers::checkType('related_activities', 'boolean', $opts['related_activities']); - XApiHelpers::checkType('attachments', 'boolean', $opts['attachments']); - XApiHelpers::checkType('ascending', 'boolean', $opts['ascending']); + if (!in_array($opts['format'], ['exact', 'canonical', 'ids'])) { + throw new Exceptions\Exception('`format` must be "exact", "canonical", or "ids".'); + } + } + + private function validateOpt($opts, $opt, $type) { + $class = '\Locker\XApi\\'.$type; + if (isset($opts[$opt])) { + Helpers::validateAtom(new $class($opts[$opt])); + } } /** From af83ebfac41616c7d5a651185a4b1b4d306fd560 Mon Sep 17 00:00:00 2001 From: Ryan Smith <0ryansmith1994@gmail.com> Date: Wed, 15 Apr 2015 14:00:12 +0100 Subject: [PATCH 06/34] Moves formatter tests. --- app/tests/Repos/Statement/FormatterTest.php | 83 +++++++++++++++++++++ 1 file changed, 83 insertions(+) create mode 100644 app/tests/Repos/Statement/FormatterTest.php diff --git a/app/tests/Repos/Statement/FormatterTest.php b/app/tests/Repos/Statement/FormatterTest.php new file mode 100644 index 0000000000..655148db5e --- /dev/null +++ b/app/tests/Repos/Statement/FormatterTest.php @@ -0,0 +1,83 @@ +formatter = new Formatter(); + } + + public function createApplication() { + $unitTesting = true; + $testEnvironment = 'testing'; + return require __DIR__ . '/../../../../bootstrap/start.php'; + } + + private function cloneObj(\stdClass $obj) { + return json_decode(json_encode($obj)); + } + + public function testIdentityStatements() { + $statement = json_decode(file_get_contents(__DIR__ . '/../../Fixtures/Repos/StatementFormatter1.json')); + $formatted = $this->formatter->identityStatement($this->cloneObj($statement)); + + $this->assertEquals(true, is_object($formatted)); + + // Asserts correct statement properties. + $this->assertEquals(true, isset($formatted->actor)); + $this->assertEquals(true, is_object($formatted->actor)); + $this->assertEquals(true, isset($formatted->verb)); + $this->assertEquals(true, is_object($formatted->verb)); + $this->assertEquals(true, isset($formatted->object)); + $this->assertEquals(true, is_object($formatted->object)); + + // Asserts correct actor properties. + $this->assertEquals(true, isset($formatted->actor->mbox)); + $this->assertEquals(true, is_string($formatted->actor->mbox)); + $this->assertEquals($statement->actor->mbox, $formatted->actor->mbox); + $this->assertEquals(true, isset($formatted->actor->objectType)); + $this->assertEquals(true, is_string($formatted->actor->objectType)); + $this->assertEquals($statement->actor->objectType, $formatted->actor->objectType); + $this->assertEquals(true, !isset($formatted->actor->name)); + + // Asserts correct object properties. + $this->assertEquals(true, isset($formatted->object->id)); + $this->assertEquals(true, is_string($formatted->object->id)); + $this->assertEquals($statement->object->id, $formatted->object->id); + $this->assertEquals(true, isset($formatted->object->objectType)); + $this->assertEquals(true, is_string($formatted->object->objectType)); + $this->assertEquals($statement->object->objectType, $formatted->object->objectType); + $this->assertEquals(true, !isset($formatted->object->definition)); + } + + public function testCanonicalStatements() { + $statement = json_decode(file_get_contents(__DIR__ . '/../../Fixtures/Repos/StatementFormatter1.json')); + $formatted = $this->formatter->canonicalStatement($this->cloneObj($statement), ['en-GB']); + + $this->assertEquals(true, is_object($formatted)); + + // Asserts correct statement properties. + $this->assertEquals(true, isset($formatted->actor)); + $this->assertEquals(true, is_object($formatted->actor)); + $this->assertEquals(true, isset($formatted->verb)); + $this->assertEquals(true, is_object($formatted->verb)); + $this->assertEquals(true, isset($formatted->object)); + $this->assertEquals(true, is_object($formatted->object)); + + // Asserts correct verb display. + $this->assertEquals(true, isset($formatted->verb->display)); + $this->assertEquals(true, is_string($formatted->verb->display)); + $this->assertEquals($statement->verb->display->{'en-GB'}, $formatted->verb->display); + + // Asserts correct object definition. + $this->assertEquals(true, isset($formatted->object->definition->name)); + $this->assertEquals(true, is_string($formatted->object->definition->name)); + $this->assertEquals($statement->object->definition->name->{'en-GB'}, $formatted->object->definition->name); + $this->assertEquals(true, isset($formatted->object->definition->description)); + $this->assertEquals(true, is_string($formatted->object->definition->description)); + $this->assertEquals($statement->object->definition->description->{'en-GB'}, $formatted->object->definition->description); + } +} From 10fc1e0ac5686cf176659566a02ce74f2977c815 Mon Sep 17 00:00:00 2001 From: Ryan Smith <0ryansmith1994@gmail.com> Date: Wed, 15 Apr 2015 14:00:28 +0100 Subject: [PATCH 07/34] Moves formatter tests. --- app/tests/Repos/StatementFormatterTest.php | 83 ---------------------- 1 file changed, 83 deletions(-) delete mode 100644 app/tests/Repos/StatementFormatterTest.php diff --git a/app/tests/Repos/StatementFormatterTest.php b/app/tests/Repos/StatementFormatterTest.php deleted file mode 100644 index 977cfcd756..0000000000 --- a/app/tests/Repos/StatementFormatterTest.php +++ /dev/null @@ -1,83 +0,0 @@ -formatter = new Formatter(); - } - - public function createApplication() { - $unitTesting = true; - $testEnvironment = 'testing'; - return require __DIR__ . '/../../../bootstrap/start.php'; - } - - private function cloneObj(\stdClass $obj) { - return json_decode(json_encode($obj)); - } - - public function testIdentityStatements() { - $statement = json_decode(file_get_contents(__DIR__ . '/../Fixtures/Repos/StatementFormatter1.json')); - $formatted = $this->formatter->identityStatement($this->cloneObj($statement)); - - $this->assertEquals(true, is_object($formatted)); - - // Asserts correct statement properties. - $this->assertEquals(true, isset($formatted->actor)); - $this->assertEquals(true, is_object($formatted->actor)); - $this->assertEquals(true, isset($formatted->verb)); - $this->assertEquals(true, is_object($formatted->verb)); - $this->assertEquals(true, isset($formatted->object)); - $this->assertEquals(true, is_object($formatted->object)); - - // Asserts correct actor properties. - $this->assertEquals(true, isset($formatted->actor->mbox)); - $this->assertEquals(true, is_string($formatted->actor->mbox)); - $this->assertEquals($statement->actor->mbox, $formatted->actor->mbox); - $this->assertEquals(true, isset($formatted->actor->objectType)); - $this->assertEquals(true, is_string($formatted->actor->objectType)); - $this->assertEquals($statement->actor->objectType, $formatted->actor->objectType); - $this->assertEquals(true, !isset($formatted->actor->name)); - - // Asserts correct object properties. - $this->assertEquals(true, isset($formatted->object->id)); - $this->assertEquals(true, is_string($formatted->object->id)); - $this->assertEquals($statement->object->id, $formatted->object->id); - $this->assertEquals(true, isset($formatted->object->objectType)); - $this->assertEquals(true, is_string($formatted->object->objectType)); - $this->assertEquals($statement->object->objectType, $formatted->object->objectType); - $this->assertEquals(true, !isset($formatted->object->definition)); - } - - public function testCanonicalStatements() { - $statement = json_decode(file_get_contents(__DIR__ . '/../Fixtures/Repos/StatementFormatter1.json')); - $formatted = $this->formatter->canonicalStatement($this->cloneObj($statement), ['en-GB']); - - $this->assertEquals(true, is_object($formatted)); - - // Asserts correct statement properties. - $this->assertEquals(true, isset($formatted->actor)); - $this->assertEquals(true, is_object($formatted->actor)); - $this->assertEquals(true, isset($formatted->verb)); - $this->assertEquals(true, is_object($formatted->verb)); - $this->assertEquals(true, isset($formatted->object)); - $this->assertEquals(true, is_object($formatted->object)); - - // Asserts correct verb display. - $this->assertEquals(true, isset($formatted->verb->display)); - $this->assertEquals(true, is_string($formatted->verb->display)); - $this->assertEquals($statement->verb->display->{'en-GB'}, $formatted->verb->display); - - // Asserts correct object definition. - $this->assertEquals(true, isset($formatted->object->definition->name)); - $this->assertEquals(true, is_string($formatted->object->definition->name)); - $this->assertEquals($statement->object->definition->name->{'en-GB'}, $formatted->object->definition->name); - $this->assertEquals(true, isset($formatted->object->definition->description)); - $this->assertEquals(true, is_string($formatted->object->definition->description)); - $this->assertEquals($statement->object->definition->description->{'en-GB'}, $formatted->object->definition->description); - } -} From 82e7f4437ae4ef01e87e94c270c61b1f1b57a774 Mon Sep 17 00:00:00 2001 From: Ryan Smith <0ryansmith1994@gmail.com> Date: Wed, 15 Apr 2015 14:00:42 +0100 Subject: [PATCH 08/34] Adds tests for IndexOptions. --- .../Repos/Statement/IndexOptionsTest.php | 143 ++++++++++++++++++ 1 file changed, 143 insertions(+) create mode 100644 app/tests/Repos/Statement/IndexOptionsTest.php diff --git a/app/tests/Repos/Statement/IndexOptionsTest.php b/app/tests/Repos/Statement/IndexOptionsTest.php new file mode 100644 index 0000000000..b2250fe472 --- /dev/null +++ b/app/tests/Repos/Statement/IndexOptionsTest.php @@ -0,0 +1,143 @@ +default_opts = [ + 'agent' => null, + 'activity' => null, + 'verb' => null, + 'registration' => null, + 'since' => null, + 'until' => null, + 'active' => true, + 'voided' => false, + 'related_activities' => false, + 'related_agents' => false, + 'ascending' => false, + 'format' => 'exact', + 'offset' => 0, + 'limit' => 100, + 'langs' => [], + 'attachments' => false, + 'lrs_id' => '1' + ]; + $this->test_opts = [ + 'agent' => (object) ['mbox' => 'mailto:test@example.com'], + 'activity' => 'http://www.example.com', + 'verb' => 'http://www.example.com', + 'registration' => '93439880-e35a-11e4-b571-0800200c9a66', + 'since' => '2015-01-01T00:00Z', + 'until' => '2015-01-01T00:00Z', + 'active' => true, + 'voided' => true, + 'related_activities' => true, + 'related_agents' => true, + 'ascending' => true, + 'format' => 'exact', + 'offset' => 0, + 'limit' => 1, + 'langs' => [], + 'attachments' => true, + 'lrs_id' => '1' + ]; + $this->invalid_opts = [ + 'agent' => (object) ['mbox' => 'test@example.com'], + 'activity' => 'zz.example.com', + 'verb' => 'zz.example.com', + 'registration' => '93439880-e35a-11e4-b571-0800200c9a66Z', + 'since' => '2015-01-01T00:00ZYX', + 'until' => '2015-01-01T00:00ZYX', + 'active' => 'invalid', + 'voided' => 'invalid', + 'related_activities' => 'invalid', + 'related_agents' => 'invalid', + 'ascending' => 'invalid', + 'format' => 'invalid', + 'offset' => -1, + 'limit' => -1, + 'langs' => 'invalid', + 'attachments' => 'invalid', + 'lrs_id' => null + ]; + } + + public function createApplication() { + $unitTesting = true; + $testEnvironment = 'testing'; + return require __DIR__ . '/../../../../bootstrap/start.php'; + } + + public function testValidOptions() { + try { + $start_opts = $this->test_opts; + $end_opts = new IndexOptions($start_opts); + + foreach ($end_opts->options as $key => $val) { + $this->assertEquals($start_opts[$key], $val); + } + } catch (\Exception $ex) { + $this->assertEquals(false, true, $ex->getMessage()); + } + } + + public function testValidCanonicalFormat() { + try { + $start_opts = $this->test_opts; + $start_opts['format'] = 'canonical'; + $end_opts = new IndexOptions($start_opts); + + foreach ($end_opts->options as $key => $val) { + $this->assertEquals($start_opts[$key], $val); + } + } catch (\Exception $ex) { + $this->assertEquals(false, true, $ex->getMessage()); + } + } + + public function testValidIdsFormat() { + try { + $start_opts = $this->test_opts; + $start_opts['format'] = 'ids'; + $end_opts = new IndexOptions($start_opts); + + foreach ($end_opts->options as $key => $val) { + $this->assertEquals($start_opts[$key], $val); + } + } catch (\Exception $ex) { + $this->assertEquals(false, true, $ex->getMessage()); + } + } + + public function testDefaultOptions() { + try { + $start_opts = $this->default_opts; + $end_opts = new IndexOptions(['lrs_id' => $this->default_opts['lrs_id']]); + + foreach ($end_opts->options as $key => $val) { + $this->assertEquals($start_opts[$key], $val); + } + } catch (\Exception $ex) { + $this->assertEquals(false, true, $ex->getMessage()); + } + } + + public function testInvalidOptions() { + foreach ($this->invalid_opts as $invalid_key => $invalid_val) { + try { + $caught = false; + $start_opts = $this->test_opts; + $start_opts[$invalid_key] = $invalid_val; + $end_opts = new IndexOptions($start_opts); + } catch (\Exception $ex) { + $caught = true; + } + $this->assertEquals(true, $caught); + } + } + +} From 6767f9159ace3a3a9e81cad3c2c96ffa6d95cff2 Mon Sep 17 00:00:00 2001 From: Ryan Smith <0ryansmith1994@gmail.com> Date: Wed, 15 Apr 2015 14:02:30 +0100 Subject: [PATCH 09/34] Corrects Shower. --- app/locker/repository/Statement/EloquentShower.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/locker/repository/Statement/EloquentShower.php b/app/locker/repository/Statement/EloquentShower.php index 40784291ea..e6f1aed2c2 100644 --- a/app/locker/repository/Statement/EloquentShower.php +++ b/app/locker/repository/Statement/EloquentShower.php @@ -21,7 +21,7 @@ class EloquentShower extends EloquentReader implements ShowerInterface { */ public function show($id, array $opts) { $opts = $this->mergeDefaultOptions($opts); - $model = $this->query->where($opts) + $model = $this->where($opts) ->where('statement.id', $id) ->where('voided', $opts['voided']) ->where('active', $opts['active']) From 364bae4e51cf3a02989a5a3a3f84d9280072359d Mon Sep 17 00:00:00 2001 From: Ryan Smith <0ryansmith1994@gmail.com> Date: Wed, 15 Apr 2015 14:59:21 +0100 Subject: [PATCH 10/34] Creates statement linker. --- .../repository/Statement/EloquentLinker.php | 149 ++++++++++++++++++ 1 file changed, 149 insertions(+) create mode 100644 app/locker/repository/Statement/EloquentLinker.php diff --git a/app/locker/repository/Statement/EloquentLinker.php b/app/locker/repository/Statement/EloquentLinker.php new file mode 100644 index 0000000000..898de1f9fb --- /dev/null +++ b/app/locker/repository/Statement/EloquentLinker.php @@ -0,0 +1,149 @@ + Mixed] $opts + */ + public function updateReferences(array $statements, array $opts) { + $this->to_update = array_map(function (\stdClass $statement) use ($opts) { + return $this->getModel($statement, $opts); + }, $statements); + + while (count($this->to_update) > 0) { + $this->upLink($this->to_update[0], [], $opts); + } + } + + /** + * Determines if a statement is a referencing statement. + * @param \stdClass $statement + * @return Boolean + */ + private function isReferencing(Model $statement) { + return ( + isset($model->statement->object->objectType) && + $model->statement->object->objectType === 'StatementRef' + ); + } + + /** + * Gets the statement as an associative array from the database. + * @param \stdClass $statement + * @param [String => Mixed] $opts + * @return [Model] + */ + private function getModel(\stdClass $statement, array $opts) { + $statement_id = $statement->id; + $model = $this->where($opts) + ->where('statement.id', $statement_id) + ->first(); + return $model; + } + + /** + * Goes up the reference chain until it reaches the top then goes down setting references. + * @param Model $model + * @param [String] $visited IDs of statements visisted in the current chain (avoids infinite loop). + * @param [String => Mixed] $opts + * @return [Model] + */ + private function upLink(Model $model, array $visited, array $opts) { + if (in_array($model->statement->id, $visited)) return []; + $visited[] = $model->statement->id; + $up_refs = $this->upRefs($model, $opts); + if ($up_refs->count() > 0) { + return $up_refs->map(function ($up_ref) use ($opts, $visited) { + if (in_array($up_ref, $this->downed)) return; + $this->downed = array_merge($this->downed, $this->upLink($up_ref, $visited, $opts)); + })->values(); + } else { + return $this->downLink($model, [], $opts); + } + } + + /** + * Goes down the reference chain setting references (refs). + * @param Model $model + * @param [String] $visited IDs of statements visisted in the current chain (avoids infinite loop). + * @param [String => Mixed] $opts + * @return [Model] + */ + private function downLink(Model $model, array $visited, array $opts) { + if (in_array($model, $visited)) { + return array_slice($visited, array_search($model, $visited)); + } + $visited[] = $model; + $down_ref = $this->downRef($model, $opts); + if ($down_ref !== null) { + $refs = $this->downLink($down_ref, $visited, $opts); + $this->setRefs($model, $refs, $opts); + $this->unQueue($model); + return array_merge([$model], $refs); + } else { + $this->unQueue($model); + return [$model]; + } + } + + /** + * Gets the statements referencing the given statement. + * @param Model $model + * @param [String => Mixed] $opts + * @return [Model] + */ + private function upRefs(Model $model, array $opts) { + return $this->where($opts) + ->where('statement.object.id', $model->statement->id) + ->where('statement.object.objectType', 'StatementRef') + ->get(); + } + + /** + * Gets the statement referred to by the given statement. + * @param Model $model + * @param [String => Mixed] $opts + * @return Model + */ + private function downRef(Model $model, array $opts) { + if (!$this->isReferencing($model)) return null; + return $this->where($opts) + ->where('statement.id', $model->statement->object->id) + ->first(); + } + + /** + * Updates the refs for the given statement. + * @param Model $model + * @param [[String => mixed]] $refs Statements that are referenced by the given statement. + * @param [String => Mixed] $opts + */ + private function setRefs(Model $model, array $refs) { + $model->refs = array_map(function ($ref) { + return $ref->statement; + }, $refs); + $model->save(); + } + + /** + * Unqueues the statement so that it doesn't get relinked. + * @param Model $model + */ + private function unQueue(Model $model) { + $updated_index = array_search($model, $this->to_update); + if ($updated_index !== false) { + array_splice($this->to_update, $updated_index, 1); + } + } +} From e4f39e4d1f8ecb345e7d84677bbaec7f0c999137 Mon Sep 17 00:00:00 2001 From: Ryan Smith <0ryansmith1994@gmail.com> Date: Wed, 15 Apr 2015 14:59:33 +0100 Subject: [PATCH 11/34] Creates statement voider. --- .../repository/Statement/EloquentVoider.php | 60 +++++++++++++++++++ 1 file changed, 60 insertions(+) create mode 100644 app/locker/repository/Statement/EloquentVoider.php diff --git a/app/locker/repository/Statement/EloquentVoider.php b/app/locker/repository/Statement/EloquentVoider.php new file mode 100644 index 0000000000..18190fd781 --- /dev/null +++ b/app/locker/repository/Statement/EloquentVoider.php @@ -0,0 +1,60 @@ + Mixed] $opts + */ + public function voidStatements(array $statements, array $opts) { + return array_map(function (\stdClass $voider) use ($opts) { + return $this->voidStatement($voider, $opts); + }, $statements); + } + + /** + * Voids a statement if it needs to be voided. + * @param \stdClass $voider + * @param [String => Mixed] $opts + */ + private function voidStatement(\stdClass $voider, array $opts) { + if (!$this->isVoiding($voider)) return; + + $voided = $this->where($opts) + ->where('statement.id', $voider->object->id) + ->first(); + + if ($voided !== null) { + if ($this->isVoidinging($voided->statement)) throw new \Exception(trans( + 'xapi.errors.void_voider' + )); + + $voided->voided = true; + $voided->save(); + } else { + throw new \Exception(trans( + 'xapi.errors.void_null' + )); + } + } + + /** + * Determines if a statement is a voiding statement. + * @param \stdClass $voider + * @return Boolean + */ + private function isVoiding(\stdClass $voider) { + return ( + isset($voider->object->id) && + $voider->object->id === 'http://adlnet.gov/expapi/verbs/voided' && + $this->isReferencing($statement) + ); + } +} From f767d6b6ac227856e4d59e2cd120e553b8bd3050 Mon Sep 17 00:00:00 2001 From: Ryan Smith <0ryansmith1994@gmail.com> Date: Wed, 15 Apr 2015 17:21:24 +0100 Subject: [PATCH 12/34] Corrects testing. --- .../repository/Statement/EloquentIndexer.php | 38 +++---- .../repository/Statement/EloquentReader.php | 2 +- .../repository/Statement/IndexOptions.php | 2 +- app/models/Statement.php | 32 +----- .../Repos/Statement/EloquentIndexerTest.php | 103 ++++++++++++++++++ .../Repos/Statement/IndexOptionsTest.php | 13 +++ 6 files changed, 138 insertions(+), 52 deletions(-) create mode 100644 app/tests/Repos/Statement/EloquentIndexerTest.php diff --git a/app/locker/repository/Statement/EloquentIndexer.php b/app/locker/repository/Statement/EloquentIndexer.php index 465556f3d2..58c6c2347f 100644 --- a/app/locker/repository/Statement/EloquentIndexer.php +++ b/app/locker/repository/Statement/EloquentIndexer.php @@ -14,22 +14,17 @@ public function count(Builder $builder, IndexOptions $opts); class EloquentIndexer extends EloquentReader implements IndexerInterface { + public function __construct() { + $this->formatter = new Formatter(); + } + /** * Gets all of the available models with the options. * @param IndexOptions $opts * @return [Model] */ public function index(IndexOptions $opts) { - return $this->where($opts); - } - - /** - * Constructs a query restricted by the given options. - * @param IndexOptions $opts - * @return Builder - */ - protected function where(IndexOptions $opts) { - $builder = parent::where($opts->options); + $builder = $this->where($opts->options); return $this->constructFilterOpts($builder, $opts, [ 'agent' => function ($value, $builder, IndexOptions $opts) { @@ -100,7 +95,7 @@ private function addWheres(Builder $builder, array $keys, $value) { private function constructFilterOpts(Builder $builder, IndexOptions $opts, array $builders) { foreach ($builders as $opt => $opt_builder) { $opt_value = $opts->getOpt($opt); - $builder = $opt_value === null ? $builder : $opt_builder($opt_value, $opt_builder, $opts); + $builder = $opt_value === null ? $builder : $opt_builder($opt_value, $builder, $opts); } return $builder; } @@ -115,16 +110,16 @@ public function format(Builder $builder, IndexOptions $opts) { // Determines the formatter to be used. $format = $opts->getOpt('format'); if ($format === 'exact') { - $formatter = function ($model, $opts) { - return $model; + $formatter = function ($statement, $opts) { + return $statement; }; } else if ($format === 'ids') { - $formatter = function ($model, $opts) { - return $this->formatter->identityStatement($model); + $formatter = function ($statement, $opts) { + return $this->formatter->identityStatement($statement); }; } else if ($format === 'canonical') { - $formatter = function ($model, $opts) { - return $this->formatter->canonicalStatement($model, $opts['langs']); + $formatter = function ($statement, $opts) { + return $this->formatter->canonicalStatement($statement, $opts['langs']); }; } else { throw new Exceptions\Exception("`$format` is not a valid format."); @@ -132,11 +127,12 @@ public function format(Builder $builder, IndexOptions $opts) { // Returns the models. return $builder - ->offset($opts->getOpt('offset')) - ->limit($opts->getOpt('limit')) + ->orderBy('statement.stored', $opts->getOpt('ascending')) + ->skip($opts->getOpt('offset')) + ->take($opts->getOpt('limit')) ->get() - ->each(function (Model $model) use ($opts) { - return $formatter($model, $opts); + ->map(function (Model $model) use ($opts, $formatter) { + return $formatter($model->statement, $opts); }); } diff --git a/app/locker/repository/Statement/EloquentReader.php b/app/locker/repository/Statement/EloquentReader.php index 5a8be9675d..b783bfc312 100644 --- a/app/locker/repository/Statement/EloquentReader.php +++ b/app/locker/repository/Statement/EloquentReader.php @@ -9,6 +9,6 @@ abstract class EloquentReader { * @return \Jenssegers\Mongodb\Eloquent\Builder */ protected function where(array $opts) { - return (new $this->model)->where('lrs', $opts['lrs_id']); + return (new $this->model)->where('lrs._id', $opts['lrs_id']); } } diff --git a/app/locker/repository/Statement/IndexOptions.php b/app/locker/repository/Statement/IndexOptions.php index 11877f3919..20f0bb26f6 100644 --- a/app/locker/repository/Statement/IndexOptions.php +++ b/app/locker/repository/Statement/IndexOptions.php @@ -12,7 +12,7 @@ public function __construct(array $opts) { } public function getOpt($opt) { - return $options[$opt]; + return $this->options[$opt]; } /** diff --git a/app/models/Statement.php b/app/models/Statement.php index 687f41c022..9a7ece1ba1 100644 --- a/app/models/Statement.php +++ b/app/models/Statement.php @@ -4,38 +4,12 @@ class Statement extends Eloquent { - /** - * Our MongoDB collection used by the model. - * - * @var string - */ protected $collection = 'statements'; + protected $hidden = ['_id', 'created_at', 'updated_at']; + protected $fillable = ['statement', 'active', 'voided', 'refs', 'lrs', 'timestamp']; - /** - * We don't need default Laravel created_at - **/ - //public $timestamps = false; - - /** - * The attributes excluded from the model's JSON form. - * - * @var array - */ - protected $hidden = array('_id', 'created_at', 'updated_at'); - - /** - * For mass assigning which we use for TC statements, - * set the fillable fields. - **/ - // protected $fillable = array('actor', 'verb', 'result', 'object', 'context', - // 'authority', 'stored', 'timestamp', 'id', 'attachments', 'version'); - - /** - * All statements belong to an LRS - * - **/ public function lrs(){ return $this->belongsTo('Lrs'); } -} \ No newline at end of file +} diff --git a/app/tests/Repos/Statement/EloquentIndexerTest.php b/app/tests/Repos/Statement/EloquentIndexerTest.php new file mode 100644 index 0000000000..bd2e019453 --- /dev/null +++ b/app/tests/Repos/Statement/EloquentIndexerTest.php @@ -0,0 +1,103 @@ +indexer = new Indexer(); + $this->lrs = $this->createLRS(); + $this->statements = [$this->createStatement(0)]; + } + + public function createApplication() { + $unitTesting = true; + $testEnvironment = 'testing'; + return require __DIR__ . '/../../../../bootstrap/start.php'; + } + + private function createLrs() { + $lrs = new \Lrs([ + 'title' => 'TestLRS', + 'api' => [], + 'owner' => [], + 'users' => [], + 'domain' => '', + ]); + + $lrs->save(); + return $lrs; + } + + protected function createStatement($id) { + $model = new \Statement($this->getStatement()); + $model->statement->id = ((string) $id).'0000000-0000-0000-0000-000000000000'; + $model->save(); + return $model; + } + + private function getStatement() { + return [ + 'statement' => json_decode(file_get_contents(__DIR__ . '../../../Fixtures/statement.json')), + 'active' => true, + 'voided' => false, + 'refs' => [], + 'timestamp' => new \MongoDate(strtotime('now')), + 'lrs' => [ + '_id' => $this->lrs->_id + ] + ]; + } + + public function testIndex() { + $opts = new IndexOptions([ + 'lrs_id' => $this->lrs->_id + ]); + $result = $this->indexer->index($opts); + + $this->assertEquals(true, is_object($result)); + $this->assertEquals('Jenssegers\Mongodb\Eloquent\Builder', get_class($result)); + } + + public function testFormat() { + $opts = new IndexOptions([ + 'lrs_id' => $this->lrs->_id + ]); + $result = $this->indexer->index($opts); + $result = $this->indexer->format($result, $opts); + + $this->assertEquals(true, is_object($result)); + $this->assertEquals('Illuminate\Database\Eloquent\Collection', get_class($result)); + $this->assertEquals(count($this->statements), $result->count()); + $result->each(function ($statement) { + $this->assertEquals(true, is_array($statement)); + $this->assertEquals(true, isset($statement['id'])); + $this->assertEquals(true, is_string($statement['id'])); + $expected_statement = $this->getStatement()['statement']; + $expected_statement->id = $statement['id']; + $this->assertEquals(json_encode($expected_statement), json_encode($statement)); + }); + } + + public function testCount() { + $opts = new IndexOptions([ + 'lrs_id' => $this->lrs->_id + ]); + $result = $this->indexer->index($opts); + $result = $this->indexer->count($result, $opts); + + $this->assertEquals(true, is_int($result)); + $this->assertEquals(count($this->statements), $result); + } + + public function tearDown() { + $this->lrs->delete(); + foreach ($this->statements as $statement) { + $statement->delete(); + } + parent::tearDown(); + } +} diff --git a/app/tests/Repos/Statement/IndexOptionsTest.php b/app/tests/Repos/Statement/IndexOptionsTest.php index b2250fe472..37310e77b9 100644 --- a/app/tests/Repos/Statement/IndexOptionsTest.php +++ b/app/tests/Repos/Statement/IndexOptionsTest.php @@ -140,4 +140,17 @@ public function testInvalidOptions() { } } + public function testGetOpts() { + try { + $start_opts = $this->test_opts; + $end_opts = new IndexOptions($start_opts); + + foreach ($start_opts as $key => $val) { + $this->assertEquals($val, $end_opts->getOpt($key)); + } + } catch (\Exception $ex) { + $this->assertEquals(false, true, $ex->getMessage()); + } + } + } From 9fdbc92b6e6dcae03be6d33cdc5c3a6567a3f25e Mon Sep 17 00:00:00 2001 From: Ryan Smith <0ryansmith1994@gmail.com> Date: Thu, 16 Apr 2015 16:21:46 +0100 Subject: [PATCH 13/34] Continues statement repo splitting. --- app/locker/helpers/Helpers.php | 27 + .../repository/Statement/EloquentIndexer.php | 6 +- .../repository/Statement/EloquentInserter.php | 100 ++ .../repository/Statement/EloquentLinker.php | 28 +- .../repository/Statement/EloquentReader.php | 4 +- .../Statement/EloquentRepository.php | 54 + .../repository/Statement/EloquentShower.php | 32 +- .../Statement/EloquentStatementRepository.php | 970 ------------------ .../repository/Statement/EloquentStorer.php | 90 ++ .../repository/Statement/EloquentVoider.php | 10 +- .../repository/Statement/FileAttacher.php | 13 + .../repository/Statement/IndexOptions.php | 144 +-- app/locker/repository/Statement/Options.php | 71 ++ .../repository/Statement/ShowOptions.php | 13 + .../Statement/StatementRepository.php | 12 - .../repository/Statement/StoreOptions.php | 9 + .../Repos/Statement/EloquentIndexerTest.php | 54 +- .../Repos/Statement/EloquentShowerTest.php | 75 ++ app/tests/Repos/Statement/EloquentTest.php | 59 ++ app/tests/Repos/Statement/FormatterTest.php | 2 +- .../Repos/Statement/IndexOptionsTest.php | 196 ++-- app/tests/Repos/Statement/OptionsTest.php | 74 ++ app/tests/Repos/Statement/ShowOptionsTest.php | 24 + .../Repos/Statement/StoreOptionsTest.php | 26 + 24 files changed, 796 insertions(+), 1297 deletions(-) create mode 100644 app/locker/repository/Statement/EloquentInserter.php create mode 100644 app/locker/repository/Statement/EloquentRepository.php delete mode 100644 app/locker/repository/Statement/EloquentStatementRepository.php create mode 100644 app/locker/repository/Statement/EloquentStorer.php create mode 100644 app/locker/repository/Statement/FileAttacher.php create mode 100644 app/locker/repository/Statement/Options.php create mode 100644 app/locker/repository/Statement/ShowOptions.php delete mode 100644 app/locker/repository/Statement/StatementRepository.php create mode 100644 app/locker/repository/Statement/StoreOptions.php create mode 100644 app/tests/Repos/Statement/EloquentShowerTest.php create mode 100644 app/tests/Repos/Statement/EloquentTest.php create mode 100644 app/tests/Repos/Statement/OptionsTest.php create mode 100644 app/tests/Repos/Statement/ShowOptionsTest.php create mode 100644 app/tests/Repos/Statement/StoreOptionsTest.php diff --git a/app/locker/helpers/Helpers.php b/app/locker/helpers/Helpers.php index 6d8f22316f..e828315a22 100644 --- a/app/locker/helpers/Helpers.php +++ b/app/locker/helpers/Helpers.php @@ -157,4 +157,31 @@ static function validateAtom(XAPIAtom $atom, $trace = null) { }, $errors)); } } + + /** + * Makes a new UUID. + * @return String Generated UUID. + */ + static function makeUUID() { + $remote_addr = isset($_SERVER['REMOTE_ADDR']) ? $_SERVER['REMOTE_ADDR'] : 'LL'; + mt_srand(crc32(serialize([microtime(true), $remote_addr, 'ETC']))); + + return sprintf('%04x%04x-%04x-%04x-%04x-%04x%04x%04x', + mt_rand(0, 0xffff), mt_rand(0, 0xffff), + mt_rand(0, 0xffff), + mt_rand(0, 0x0fff) | 0x4000, + mt_rand(0, 0x3fff) | 0x8000, + mt_rand(0, 0xffff), mt_rand(0, 0xffff), mt_rand(0, 0xffff) + ); + } + + /** + * Gets the current date and time in ISO format using the current timezone. + * @return String Current ISO date and time. + */ + static function getCurrentDate() { + $current_date = \DateTime::createFromFormat('U.u', sprintf('%.4f', microtime(true))); + $current_date->setTimezone(new \DateTimeZone(\Config::get('app.timezone'))); + return $current_date->format('Y-m-d\TH:i:s.uP'); + } } diff --git a/app/locker/repository/Statement/EloquentIndexer.php b/app/locker/repository/Statement/EloquentIndexer.php index 58c6c2347f..a0d3a8654b 100644 --- a/app/locker/repository/Statement/EloquentIndexer.php +++ b/app/locker/repository/Statement/EloquentIndexer.php @@ -14,6 +14,8 @@ public function count(Builder $builder, IndexOptions $opts); class EloquentIndexer extends EloquentReader implements IndexerInterface { + protected $formatter; + public function __construct() { $this->formatter = new Formatter(); } @@ -24,7 +26,7 @@ public function __construct() { * @return [Model] */ public function index(IndexOptions $opts) { - $builder = $this->where($opts->options); + $builder = $this->where($opts); return $this->constructFilterOpts($builder, $opts, [ 'agent' => function ($value, $builder, IndexOptions $opts) { @@ -119,7 +121,7 @@ public function format(Builder $builder, IndexOptions $opts) { }; } else if ($format === 'canonical') { $formatter = function ($statement, $opts) { - return $this->formatter->canonicalStatement($statement, $opts['langs']); + return $this->formatter->canonicalStatement($statement, $opts->getOpt('langs')); }; } else { throw new Exceptions\Exception("`$format` is not a valid format."); diff --git a/app/locker/repository/Statement/EloquentInserter.php b/app/locker/repository/Statement/EloquentInserter.php new file mode 100644 index 0000000000..d7f10c2c65 --- /dev/null +++ b/app/locker/repository/Statement/EloquentInserter.php @@ -0,0 +1,100 @@ +checkForConflict($statement, $opts); + return $this->constructModel($statement, $opts); + }, $statements); + + return $this->insertModels($models, $opts); + } + + /** + * Checks for a duplicate statement with the given options. + * @param [\stdClass] $statements + * @param StoreOptions $opts + * @throws Exceptions\Conflict + */ + private function checkForConflict(\stdClass $statement, StoreOptions $opts) { + $duplicate = $this->where($opts) + ->where('statement.id', $statement->id) + ->first(); + + if ($duplicate === null) return; + $duplicate =json_decode(json_encode($duplicate->statement)); + $this->compareForConflict($statement, $duplicate); + } + + /** + * Compares two statements. + * Throws Exceptions\Conflict if the two statements match. + * @param \stdClass $statement_x + * @param \stdClass $statement_y + * @throws Exceptions\Conflict + */ + private function compareForConflict(\stdClass $statement_x, \stdClass $statement_y) { + $encoded_x = json_encode($statement_x); + $encoded_y = json_encode($statement_y); + $decoded_x = $this->decodeStatementMatch($encoded_x); + $decoded_y = $this->decodeStatementMatch($encoded_y); + if ($decoded_x !== $decoded_y) { + throw new Exceptions\Conflict( + "Conflicts\r\n`$encoded_x`\r\n`$encoded_y`." + ); + }; + } + + /** + * Decodes the encoded statement. + * Removes properties not necessary for matching. + * @param String $encoded_statement + * @return [String => Mixed] $decoded_statement + */ + private function decodeStatementMatch($encoded_statement) { + $decoded_statement = json_decode($encoded_x, true); + array_multisort($decoded_statement); + ksort($decoded_statement); + unset($decoded_statement['stored']); + unset($decoded_statement['authority']); + return $decoded_statement; + } + + /** + * Constructs a model from the given statement and options. + * @param \stdClass $statement + * @param StoreOptions $opts + * @return [String => Mixed] $model + */ + private function constructModel(\stdClass $statement, StoreOptions $opts) { + return [ + 'lrs' => ['_id' => $opts->getOpt('lrs_id')], + 'statement' => $statement, + 'active' => false, + 'voided' => false, + 'timestamp' => new MongoDate(strtotime($statement->timestamp)) + ]; + } + + /** + * Inserts models with the given options. + * @param [[String => Mixed]] $models + * @param StoreOptions $opts + */ + private function insertModels(array $models, StoreOptions $opts) { + return $this->where($opts)->insert($models); + } +} diff --git a/app/locker/repository/Statement/EloquentLinker.php b/app/locker/repository/Statement/EloquentLinker.php index 898de1f9fb..6688abc635 100644 --- a/app/locker/repository/Statement/EloquentLinker.php +++ b/app/locker/repository/Statement/EloquentLinker.php @@ -3,7 +3,7 @@ use \Illuminate\Database\Eloquent\Model as Model; interface LinkerInterface { - public function updateReferences(array $statements, array $opts); + public function updateReferences(array $statements, StoreOptions $opts); } class EloquentLinker extends EloquentReader implements LinkerInterface { @@ -14,9 +14,9 @@ class EloquentLinker extends EloquentReader implements LinkerInterface { /** * Updates statement references. * @param [\stdClass] $statements - * @param [String => Mixed] $opts + * @param StoreOptions $opts */ - public function updateReferences(array $statements, array $opts) { + public function updateReferences(array $statements, StoreOptions $opts) { $this->to_update = array_map(function (\stdClass $statement) use ($opts) { return $this->getModel($statement, $opts); }, $statements); @@ -41,10 +41,10 @@ private function isReferencing(Model $statement) { /** * Gets the statement as an associative array from the database. * @param \stdClass $statement - * @param [String => Mixed] $opts + * @param StoreOptions $opts * @return [Model] */ - private function getModel(\stdClass $statement, array $opts) { + private function getModel(\stdClass $statement, StoreOptions $opts) { $statement_id = $statement->id; $model = $this->where($opts) ->where('statement.id', $statement_id) @@ -56,10 +56,10 @@ private function getModel(\stdClass $statement, array $opts) { * Goes up the reference chain until it reaches the top then goes down setting references. * @param Model $model * @param [String] $visited IDs of statements visisted in the current chain (avoids infinite loop). - * @param [String => Mixed] $opts + * @param StoreOptions $opts * @return [Model] */ - private function upLink(Model $model, array $visited, array $opts) { + private function upLink(Model $model, array $visited, StoreOptions $opts) { if (in_array($model->statement->id, $visited)) return []; $visited[] = $model->statement->id; $up_refs = $this->upRefs($model, $opts); @@ -77,10 +77,10 @@ private function upLink(Model $model, array $visited, array $opts) { * Goes down the reference chain setting references (refs). * @param Model $model * @param [String] $visited IDs of statements visisted in the current chain (avoids infinite loop). - * @param [String => Mixed] $opts + * @param StoreOptions $opts * @return [Model] */ - private function downLink(Model $model, array $visited, array $opts) { + private function downLink(Model $model, array $visited, StoreOptions $opts) { if (in_array($model, $visited)) { return array_slice($visited, array_search($model, $visited)); } @@ -100,10 +100,10 @@ private function downLink(Model $model, array $visited, array $opts) { /** * Gets the statements referencing the given statement. * @param Model $model - * @param [String => Mixed] $opts + * @param StoreOptions $opts * @return [Model] */ - private function upRefs(Model $model, array $opts) { + private function upRefs(Model $model, StoreOptions $opts) { return $this->where($opts) ->where('statement.object.id', $model->statement->id) ->where('statement.object.objectType', 'StatementRef') @@ -113,10 +113,10 @@ private function upRefs(Model $model, array $opts) { /** * Gets the statement referred to by the given statement. * @param Model $model - * @param [String => Mixed] $opts + * @param StoreOptions $opts * @return Model */ - private function downRef(Model $model, array $opts) { + private function downRef(Model $model, StoreOptions $opts) { if (!$this->isReferencing($model)) return null; return $this->where($opts) ->where('statement.id', $model->statement->object->id) @@ -127,7 +127,7 @@ private function downRef(Model $model, array $opts) { * Updates the refs for the given statement. * @param Model $model * @param [[String => mixed]] $refs Statements that are referenced by the given statement. - * @param [String => Mixed] $opts + * @param StoreOptions $opts */ private function setRefs(Model $model, array $refs) { $model->refs = array_map(function ($ref) { diff --git a/app/locker/repository/Statement/EloquentReader.php b/app/locker/repository/Statement/EloquentReader.php index b783bfc312..75cc51a809 100644 --- a/app/locker/repository/Statement/EloquentReader.php +++ b/app/locker/repository/Statement/EloquentReader.php @@ -8,7 +8,7 @@ abstract class EloquentReader { * @param [String => Mixed] $opts * @return \Jenssegers\Mongodb\Eloquent\Builder */ - protected function where(array $opts) { - return (new $this->model)->where('lrs._id', $opts['lrs_id']); + protected function where(Options $opts) { + return (new $this->model)->where('lrs._id', $opts->getOpt('lrs_id')); } } diff --git a/app/locker/repository/Statement/EloquentRepository.php b/app/locker/repository/Statement/EloquentRepository.php new file mode 100644 index 0000000000..a2fb5d5a6f --- /dev/null +++ b/app/locker/repository/Statement/EloquentRepository.php @@ -0,0 +1,54 @@ +storer = new EloquentStorer(); + $this->indexer = new EloquentIndexer(); + $this->shower = new EloquentShower(); + } + + /** + * Stores statements and attachments with the given options. + * @param [\stdClass] $statements + * @param [\stdClass] $attachments + * @param [String => Mixed] $opts + * @return [String] UUIDs of the stored statements. + */ + public function store(array $statements, array $attachments, array $opts) { + return $this->storer->store($statements, $attachments, new StoreOptions($opts)); + } + + /** + * Gets all of the available models with the options. + * @param [String => Mixed] $opts + * @return [[\stdClass], Int] Array containing the statements and count. + */ + public function index(array $opts) { + $opts = new IndexOptions($opts); + $builder = $this->indexer->index($opts); + return [ + $this->indexer->format($builder, $opts), + $this->indexer->count($builder, $opts) + ]; + } + + /** + * Gets the model with the given ID and options. + * @param String $id ID to match. + * @param [String => Mixed] $opts + * @return [\stdClass] + */ + public function show($id, array $opts) { + return $this->shower->show($id, new ShowOptions($opts)); + } +} \ No newline at end of file diff --git a/app/locker/repository/Statement/EloquentShower.php b/app/locker/repository/Statement/EloquentShower.php index e6f1aed2c2..b6c57fee60 100644 --- a/app/locker/repository/Statement/EloquentShower.php +++ b/app/locker/repository/Statement/EloquentShower.php @@ -1,14 +1,11 @@ Mixed] $opts + * @param ShowOptions $opts * @return Model */ - public function show($id, array $opts) { - $opts = $this->mergeDefaultOptions($opts); + public function show($id, ShowOptions $opts) { $model = $this->where($opts) ->where('statement.id', $id) - ->where('voided', $opts['voided']) - ->where('active', $opts['active']) + ->where('voided', $opts->getOpt('voided')) + ->where('active', $opts->getOpt('active')) ->first(); if ($model === null) throw new Exceptions\NotFound($id, $this->model); - return $model; + return $this->format($model); } /** @@ -38,18 +34,6 @@ public function show($id, array $opts) { * @return Model */ public function format(Model $model) { - return $model->statement; - } - - /** - * Returns all of the index options set to their default or given value (using the given options). - * @param [String => mixed] $opts - * @return [String => mixed] - */ - protected function mergeDefaultOptions(array $opts) { - return array_merge([ - 'voided' => false, - 'active' => true - ], $opts); + return json_decode(json_encode($model->statement)); } } diff --git a/app/locker/repository/Statement/EloquentStatementRepository.php b/app/locker/repository/Statement/EloquentStatementRepository.php deleted file mode 100644 index 4dd4a49706..0000000000 --- a/app/locker/repository/Statement/EloquentStatementRepository.php +++ /dev/null @@ -1,970 +0,0 @@ -statement = $statement; - $this->activity = $activity; - $this->query = $query; - } - - /** - * Gets the statement with the given $id from the lrs (with the $lrsId). - * @param UUID $lrsId - * @param UUID $id - * @param boolean $voided determines if the statement is voided. - * @param boolean $active determines if the statement is active. - * @return Builder - */ - public function show($lrsId, $id, $voided = false, $active = true) { - return $this->query->where($lrsId, [ - ['statement.id', '=', $id], - ['voided', '=', $voided], - ['active', '=', $active] - ]); - } - - /** - * Gets statements from the lrs (with the $lrsId) that match the $filters. - * @param UUID $lrsId - * @param [StatementFilter] $filters - * @param [StatementFilter] $options - * @return Builder - */ - public function index($lrsId, array $filters, array $options) { - $where = []; - - // Defaults filters. - $filters = array_merge([ - 'agent' => null, - 'activity' => null, - 'verb' => null, - 'registration' => null, - 'since' => null, - 'until' => null, - 'active' => true, - 'voided' => false - ], $filters); - - // Defaults options. - $options = array_merge([ - 'related_activities' => false, - 'related_agents' => false, - 'ascending' => false, - 'format' => 'exact', - 'offset' => 0, - 'limit' => self::DEFAULT_LIMIT - ], $options); - - // Checks params. - if ($options['offset'] < 0) throw new Exceptions\Exception('`offset` must be a positive interger.'); - if ($options['limit'] < 0) throw new Exceptions\Exception('`limit` must be a positive interger.'); - if (!in_array($options['format'], ['ids', 'exact', 'canonical'])) { - throw new Exceptions\Exception('`format` must be `ids`, `exact` or `canonical`.'); - } - - // Filters by date. - if (isset($filters['since'])) $where[] = ['statement.stored', '>', $filters['since']]; - if (isset($filters['until'])) $where[] = ['statement.stored', '<', $filters['until']]; - if (isset($filters['active'])) $where[] = ['active', '=', $filters['active']]; - if (isset($filters['voided'])) $where[] = ['voided', '=', $filters['voided']]; - $statements = $this->query->where($lrsId, $where); - - // Adds filters that don't have options. - $statements = $this->addFilter($statements, $filters['verb'], [ - 'statement.verb.id' - ]); - $statements = $this->addFilter($statements, $filters['registration'], [ - 'statement.context.registration' - ]); - - // Filters by activity. - $statements = $this->addOptionFilter($statements, $filters['activity'], $options['related_activities'], [ - 'statement.object.id' - ], [ - 'statement.context.contextActivities.parent.id', - 'statement.context.contextActivities.grouping.id', - 'statement.context.contextActivities.category.id', - 'statement.context.contextActivities.other.id' - ]); - - // Filters by agent. - $agent = $filters['agent']; - $identifier = $this->getIdentifier($agent); - if (isset($agent) && !is_array($agent)) throw new Exceptions\Exception('Invalid agent'); - $agent = isset($agent) && isset($agent[$identifier]) ? $agent[$identifier] : null; - - // Fixes https://github.com/LearningLocker/learninglocker/issues/519. - if ($identifier === 'account') { - $statements = $this->addOptionFilter($statements, $agent['name'], $options['related_agents'], [ - 'statement.actor.'.$identifier.'.name', - 'statement.object.'.$identifier.'.name' - ], [ - 'statement.authority.'.$identifier.'.name', - 'statement.context.instructor.'.$identifier.'.name', - 'statement.context.team.'.$identifier.'.name' - ]); - $statements = $this->addOptionFilter($statements, $agent['homePage'], $options['related_agents'], [ - 'statement.actor.'.$identifier.'.homePage', - 'statement.object.'.$identifier.'.homePage' - ], [ - 'statement.authority.'.$identifier.'.homePage', - 'statement.context.instructor.'.$identifier.'.homePage', - 'statement.context.team.'.$identifier.'.homePage' - ]); - } else { - $statements = $this->addOptionFilter($statements, $agent, $options['related_agents'], [ - 'statement.actor.'.$identifier, - 'statement.object.'.$identifier - ], [ - 'statement.authority.'.$identifier, - 'statement.context.instructor.'.$identifier, - 'statement.context.team.'.$identifier - ]); - } - - // Uses ordering. - if (isset($options['ascending']) && $options['ascending'] === true) { - $statements = $statements->orderBy('statement.stored', 'ASC'); - } else { - $statements = $statements->orderBy('statement.stored', 'DESC'); - } - - return $statements; - } - - /** - * Gets the identifier of the agent. - * @param array $agent - * @return string identifier (mbox, openid, account). - */ - private function getIdentifier($agent) { - if (isset($agent)) { - if (isset($agent['mbox'])) return 'mbox'; - if (isset($agent['openid'])) return 'openid'; - if (isset($agent['account'])) return 'account'; - if (isset($agent['mbox_sha1sum'])) return 'mbox_sha1sum'; - } else { - return 'actor'; - } - } - - /** - * Returns $statements where the $value matches any of the $keys. - * @param Builder $statements - * @param mixed $value - * @param array $keys - * @return Builder - */ - private function addFilter(Builder $statements, $value, array $keys) { - if (!isset($value)) return $statements; - - // Adds keys for sub statement and statement references. - foreach ($keys as $key) { - $keys[] = 'refs.'.substr($key, 10); - } - - return $this->orWhere($statements, $value, $keys); - } - - /** - * Filters $statements with an options. - * @param Builder $statements Statements to be filtered. - * @param mixed $value Value to match against $keys. - * @param boolean $use_broad - * @param array $specific Keys to be search regardless of $use_broad. - * @param array $broad Addtional keys to be searched when $use_broad is true. - * @return Builder - */ - private function addOptionFilter(Builder $statements, $value, $use_broad, array $specific, array $broad) { - $keys = $specific; - - if ($use_broad === true) { - $keys = array_merge($keys, $broad); - - // Adds keys for sub statement. - foreach ($keys as $key) { - $keys[] = 'statement.object.'.substr($key, 10); - } - } - - return $this->addFilter($statements, $value, $keys); - } - - /** - * Returns $statements where the $value matches any of the $keys. - * @param Builder $statements - * @param mixed $value - * @param array $keys - * @return Builder - */ - private function orWhere(Builder $statements, $value, array $keys) { - return $statements->where(function (Builder $query) use ($keys, $value) { - foreach ($keys as $key) { - $query->orWhere($key, $value); - } - return $query; - }); - } - - /** - * Converts statements in the "canonical" format as defined by the spec. - * https://github.com/adlnet/xAPI-Spec/blob/master/xAPI.md#723-getstatements - * @param [statements] - * @return [statements] - */ - public function toCanonical(array $statements, array $langs) { - foreach ($statements as $index => $statement) { - $statements[$index]['statement'] = $this->getStatementCanonical($statement['statement'], $langs); - } - return $statements; - } - - /** - * Converts statements in the "ids" format as defined by the spec. - * https://github.com/adlnet/xAPI-Spec/blob/master/xAPI.md#723-getstatements - * @param [statements] - * @return [statements] - */ - public function toIds(array $statements) { - foreach ($statements as $index => $statement) { - $statements[$index]['statement'] = $this->getStatementIds($statement['statement']); - } - return $statements; - } - - /** - * Attempts to convert a $langMap to a single string using a relevant language from $langs. - * @param [LanguageMap] $langMap - * @param [Language] $langs - * @return String/[LanguageMap] - */ - private function canonicalise(array $langMap, array $langs) { - foreach ($langs as $lang) { - if (isset($langMap[$lang])) { - return $langMap[$lang]; - } - } - return $langMap; - } - - /** - * Canonicalises some parts of the $statement as defined by the spec using $langs. - * https://github.com/adlnet/xAPI-Spec/blob/master/xAPI.md#723-getstatements - * @param Statement $statement - * @param [Language] $langs - * @return Statement - */ - private function getStatementCanonical(array $statement, array $langs) { - if (isset($statement['object']['definition']['name'])) { - $statement['object']['definition']['name'] = $this->canonicalise( - $statement['object']['definition']['name'], - $langs - ); - } - if (isset($statement['object']['definition']['description'])) { - $statement['object']['definition']['description'] = $this->canonicalise( - $statement['object']['definition']['description'], - $langs - ); - } - return $statement; - } - - /** - * Gets the identifier key of an $agent. - * @param Agent $actor - * @return string - */ - private function getAgentIdentifier($actor) { - if (isset($actor['mbox'])) return 'mbox'; - if (isset($actor['account'])) return 'account'; - if (isset($actor['openid'])) return 'openid'; - if (isset($actor['mbox_sha1sum'])) return 'mbox_sha1sum'; - return null; - } - - /** - * Ids some parts of the $statement as defined by the spec. - * https://github.com/adlnet/xAPI-Spec/blob/master/xAPI.md#723-getstatements - * @param Statement $statement - * @return Statement - */ - private function getStatementIds(array $statement) { - $actor = $statement['actor']; - - // Processes an anonymous group or actor. - if (isset($actor['objectType']) && $actor['objectType'] === 'Group' && $this->getAgentIdentifier($actor) === null) { - $members = []; - foreach ($actor['members'] as $member) { - $identifier = $this->getAgentIdentifier($member); - $members[] = [ - $identifier => $member[$identifier] - ]; - } - $actor['members'] = $members; - } else { - $identifier = $this->getAgentIdentifier($actor); - $actor = [ - $identifier => $actor[$identifier], - 'objectType' => isset($actor['objectType']) ? $actor['objectType'] : 'Agent' - ]; - } - - // Replace parts of the statements. - $statement['actor'] = $actor; - $identifier = $this->getAgentIdentifier($statement['object']) ?: 'id'; - $statement['object'] = [ - $identifier => $statement['object'][$identifier], - 'objectType' => isset($statement['object']['objectType']) ? $statement['object']['objectType'] : 'Activity' - ]; - - return $statement; - } - - /** - * Constructs the authority. - * https://github.com/adlnet/xAPI-Spec/blob/master/xAPI.md#authority - * @return Authority - */ - private function constructAuthority() { - $client = (new \Client) - ->where('api.basic_key', \LockerRequest::getUser()) - ->where('api.basic_secret', \LockerRequest::getPassword()) - ->first(); - - if ($client != null && isset($client['authority'])) { - return json_decode(json_encode($client['authority'])); - } else { - $site = \Site::first(); - return (object) [ - 'name' => $site->name, - 'mbox' => 'mailto:' . $site->email, - 'objectType' => 'Agent' - ]; - } - } - - /** - * Validates $statements. - * @param [Statement] $statements - * @return [Statement] Valid statements. - */ - private function validateStatements(array $statements, \Lrs $lrs) { - $statements = $this->removeDuplicateStatements($statements, $lrs); - $authority = $this->constructAuthority(); - $void_statements = []; - - foreach ($statements as $index => $statement) { - $statement->setProp('authority', $authority); - $errors = array_map(function ($error) { - return (string) $error->addTrace('statement'); - }, $statement->validate()); - - if (!empty($errors)) { - throw new Exceptions\Validation($errors); - } else { - if ($this->isVoiding($statement->getValue())) { - $void_statements[] = $statement->getPropValue('object.id'); - } - } - } - if ($void_statements) { - $this->validateVoid($statements, $lrs, $void_statements); - } - return $statements; - } - - /** - * Check that all void reference ids exist in the database and are not themselves void statements - * @param array $statements - * @param Lrs $lrs - * @param array $references - * @throws \Exception - */ - private function validateVoid(array $statements, \Lrs $lrs, array $references) { - $count = count($references); - $reference_count = $this->statement - ->where('lrs._id', $lrs->_id) - ->whereIn('statement.id', $references) - ->where('statement.verb.id', '<>', "http://adlnet.gov/expapi/verbs/voided") - ->count(); - if ($reference_count != $count) { - throw new Exceptions\Exception('Voiding invalid or nonexistant statement'); - } - } - - /** - * Remove duplicate statements and generate ids - * - * @param array $statements - * @param \Lrs $lrs - * @return array - */ - private function removeDuplicateStatements(array $statements, \Lrs $lrs) { - $new_id_count = 0; - $new_statements = []; - $indexed_statements = []; - foreach($statements as $index => $statement) { - $statement_id = $statement->getPropValue('id'); - if ($statement_id !== null) { - if (isset($this->sent_ids[$statement_id])) { - $sent_statement = json_encode($this->sent_ids[$statement_id]); - $current_statement = json_encode($statement); - $this->checkMatch($new_statement, $current_statement); - unset($statements[$index]); - } else { - $this->sent_ids[$statement_id] = $statement; - $indexed_statements[$statement_id] = $statement; - } - } else { - $new_statements[] = $statement; - } - } - - if (count($new_statements)) { - $new_statements = $this->assignIds($new_statements, $lrs); - $indexed_statements = array_merge($indexed_statements, $new_statements); - } - - return $indexed_statements; - } - - /** - * @param array $statements - * @param \Lrs $lrs - * @return array List of statements with assigned id - */ - private function assignIds(array $statements, \Lrs $lrs) { - $indexed_statements = []; - $count = count($statements); - $uuids = $this->generateIds($count + 1); - $duplicates = $this->checkIdsExist($uuids, $lrs); - if ($duplicates) { - $uuids = array_diff($uuids, $duplicates); - } - while(count($uuids) < $count) { - $new_uuids = $this->generateIds($count - count($uuids)); - $duplicates = $this->checkIdsExist($new_uuids, $lrs); - if ($duplicates) { - $new_uuids = array_diff($uuids, $duplicates); - $uuids = array_merge($new_uuids); - } - } - - foreach($statements as $statement) { - $uuid = array_pop($uuids); - $statement->setProp('id', $uuid); - $indexed_statements[$uuid] = $statement; - } - return $indexed_statements; - } - - private function checkMatch($new_statement, $old_statement) { - $new_statement_obj = \Locker\XApi\Statement::createFromJson($new_statement); - $old_statement_obj = \Locker\XApi\Statement::createFromJson($old_statement); - $new_statement = json_decode($new_statement_obj->toJson(), true); - $old_statement = json_decode($old_statement_obj->toJson(), true); - array_multisort($new_statement); - array_multisort($old_statement); - ksort($new_statement); - ksort($old_statement); - unset($new_statement['stored']); - unset($old_statement['stored']); - if ($new_statement !== $old_statement) { - $new_statement = $new_statement_obj->toJson(); - $old_statement = $old_statement_obj->toJson(); - throw new Exceptions\Conflict( - "Conflicts\r\n`$new_statement`\r\n`$old_statement`." - ); - }; - } - - /** - * Check lrs for list of statement ids, optional list of statements by id for comparison - * - * @param array $uuids - * @param \Lrs $lrs - * @param array $statements - * @return array List of duplicate ids - */ - private function checkIdsExist(array $uuids, \Lrs $lrs, array $statements=null) { - $duplicates = array(); - - if ($uuids) { - $existingModels = $this->statement - ->where('lrs._id', $lrs->_id) - ->whereIn('statement.id', $uuids) - ->get(); - - if(!$existingModels->isEmpty()) { - foreach($existingModels as $existingModel) { - $existingStatement = $existingModel->statement; - $id = $existingStatement['id']; - $duplicates[] = $id; - if ($statements && isset($statements[$id])) { - $statement = $statements[$id]; - $this->checkMatch($statement->toJson(), json_encode($existingStatement)); - } - } - } - } - return $duplicates; - } - - /** - * Generate an array of uuids of size $count - * - * @param integer $count - * @return array List of uuids - */ - private function generateIds($count) { - $uuids = array(); - $validator = new \app\locker\statements\xAPIValidation(); - $i = 1; - while ($i <= $count) { - $uuid = $validator->makeUUID(); - if (isset($this->sent_ids[$uuid])) { - continue; - } - $i++; - $uuids[] = $uuid; - } - - return $uuids; - } - - /** - * Create statements. - * @param [Statement] $statements - * @param Lrs $lrs - * @return array list of statements - */ - private function createStatements(array $statements, \Lrs $lrs) { - if (count($this->sent_ids)) { - // check for duplicates from statements with pre-assigned ids - $this->checkIdsExist(array_keys($this->sent_ids), $lrs, $statements); - } - - // Replaces '.' in keys with '&46;'. - $statements = array_map(function (\Locker\XApi\Statement $statement) use ($lrs) { - $replaceFullStop = function ($object, $replaceFullStop) { - if ($object instanceof \Locker\XApi\Element) { - $prop_keys = array_keys(get_object_vars($object->getValue())); - foreach ($prop_keys as $prop_key) { - $new_prop_key = str_replace('.', '&46;', $prop_key); - $prop_value = $object->getProp($prop_key); - $new_value = $replaceFullStop($prop_value, $replaceFullStop); - $object->unsetProp($prop_key); - $object->setProp($new_prop_key, $new_value); - } - return $object; - } else { - return $object; - } - }; - $replaceFullStop($statement, $replaceFullStop); - - return $this->makeStatement($statement, $lrs); - }, $statements); - - $this->statement->where('lrs._id', $lrs->id)->insert(array_values($statements)); - return $statements; - } - - /** - * Sets references - * @param array $statements - * @param \Lrs $lrs - * @return array list of statements with references - */ - public function updateReferences(array $statements, \Lrs $lrs) { - foreach($statements as $id => $statement) { - if ($this->isReferencing($statement['statement'])) { - // Finds the statement that it references. - $refs = []; - $this->recursiveCheckReferences($statements, $lrs, $refs, $statement['statement']->object->id); - // Updates the refs. - if ($refs) { - $refs = array_values($refs); - $statements[$id]['refs'] = $refs; - $this->statement - ->where('lrs._id', $lrs->id) - ->where('statement.id', $id)->update([ - 'refs' => $refs - ]); - } - } - } - $this->updateReferrers($statements, $lrs); - return $statements; - } - - private function recursiveCheckReferences(array $statements, \Lrs $lrs, array &$refs, $id) { - // check if $id refers to a statement being inserted - if (isset($refs[$id])) { - return $refs; - } - - if (isset($statements[$id])) { - $s = $statements[$id]; - $refs[$id] = $s->statement; - if ($this->isReferencing($s->statement)) { - $s_id = $s->statement->getPropValue('object.id'); - $this->recursiveCheckReferences($statements, $lrs, $refs, $s_id); - } - } else { - $reference = $this->query->where($lrs->_id, [ - ['statement.id', '=', $id] - ])->first(); - if ($reference) { - $refs[$id] = $reference->statement; - if ($this->isReferencing((object) $reference->statement)) { - $s_id = $reference->statement['object']['id']; - $this->recursiveCheckReferences($statements, $lrs, $refs, $s_id); - } - } - } - return $refs; - } - - /** - * Adds statement to refs in a existing referrer. - * @param [Statement] $statements - * @return [Statement] - */ - private function updateReferrers(array $statements, \Lrs $lrs) { - if (count($this->sent_ids)) { - $referrers = $this->query->where($lrs->_id, [ - ['statement.object.id', 'in', array_keys($statements)], - ['statement.object.objectType', '=', 'StatementRef'], - ])->get(); - - // Updates the refs $referrers. - foreach ($referrers as $referrer) { - $statement_id = $referrer['statement']['object']['id']; - $statement = $statements[$statement_id]; - if (isset($statement['refs'])) { - $referrer->refs = array_merge([$statement['statement']], $statement['refs']); - } else { - $referrer->refs = [$statement['statement']]; - } - if (!$referrer->save()) throw new Exceptions\Exception('Failed to save referrer.'); - } - } - return $statements; - } - - private function isReferencing(\stdClass $statement) { - return ( - isset($statement->object->id) && - isset($statement->object->objectType) && - $statement->object->objectType === 'StatementRef' - ); - } - - /** - * Determines if a $statement voids another. - * @param Statement $statement - * @return boolean - */ - private function isVoiding(\stdClass $statement) { - if (($statement->verb->id === 'http://adlnet.gov/expapi/verbs/voided') && $this->isReferencing($statement)) { - return true; - } - return false; - } - - private function voidStatement($statement, $lrs) { - if (!$this->isVoiding($statement['statement'])) return $statement; - $reference = $this->query->where($lrs->_id, [ - ['statement.id', '=', $statement['statement']->object->id] - ])->first(); - $ref_statement = json_decode(json_encode($reference->statement)); - if ($this->isVoiding($ref_statement)) { - throw new Exceptions\Exception('Cannot void a voiding statement'); - } - $reference->voided = true; - if (!$reference->save()) throw new Exceptions\Exception('Failed to void statement.'); - return $statement; - } - - public function voidStatements(array $statements, \Lrs $lrs) { - return array_map(function (array $statement) use ($lrs) { - return $this->voidStatement($statement, $lrs); - }, $statements); - } - - public function activateStatements(array $statements, \Lrs $lrs) { - $updated = $this->statement->where('lrs._id', $lrs->id)->whereIn('statement.id', array_keys($statements))->update(array('active' => true)); - } - - /** - * Creates $statements in the $lrs with $attachments. - * @param [Statement] $statements - * @param \LRS $lrs - * @param string $attachments - * @return array create result (see makeCreateResult function) - */ - public function create(array $statements, \Lrs $lrs, $attachments = '') { - $statements = array_map(function (\stdClass $statement) { - return new \Locker\XApi\Statement($statement); - }, $statements); - $statements = $this->validateStatements($statements, $lrs); - $statements = $this->createStatements($statements, $lrs); - $statements = $this->updateReferences($statements, $lrs); - $statements = $this->voidStatements($statements, $lrs); - $this->activateStatements($statements, $lrs); - - // Stores the $attachments. - if ($attachments != '') { - $this->storeAttachments($attachments, $lrs->_id); - } - return array_keys($statements); - } - - /** - * Validates a $statement with an $authority. - * @param Statement $statement - * @param Authority $Authority - * @return Validator - */ - private function validateStatement(array $statement, array $authority) { - return (new \app\locker\statements\xAPIValidation())->runValidation( - $statement, - $authority - ); - } - - /** - * Makes a $statement for the current $lrs. - * @param Statement $statement - * @param LRS $lrs - * @return Statement - */ - private function makeStatement(\Locker\XApi\Statement $statement, \Lrs $lrs) { - // Uses defaults where possible. - $currentDate = $this->getCurrentDate(); - $statement->setProp('stored', $currentDate); - if ($statement->getPropValue('timestamp') === null) { - $statement->setProp('timestamp', $currentDate); - } - - // For now we store the latest submitted definition. - // @todo this will change when we have a way to determine authority to edit. - if ($statement->getPropValue('object.definition') !== null) { - $this->activity->saveActivity( - $statement->getPropValue('object.id'), - $statement->getPropValue('object.definition') - ); - } - // Create a new statement model - return [ - 'lrs' => [ - '_id' => $lrs->_id, - 'name' => $lrs->title - ], - 'statement' => $statement->getValue(), - 'active' => false, - 'voided' => false, - 'timestamp' => new \MongoDate(strtotime($statement->getPropValue('timestamp'))) - ]; - } - - /** - * Make an associative array that represents the result of creating statements. - * @param [StatementId] $ids Array of IDs of successfully created statements. - * @param boolean $success - * @param string $description Description of the result. - * @return array create result. - */ - private function makeCreateResult(array $ids, $success = false, $description = '') { - return [ - 'success' => $success, - 'ids' => $ids, - 'message' => $description - ]; - } - - /** - * Calculates the current date(consistent through xAPI header). - * @return string - */ - public function getCurrentDate() { - $current_date = \DateTime::createFromFormat('U.u', sprintf('%.4f', microtime(true))); - $current_date->setTimezone(new \DateTimeZone(\Config::get('app.timezone'))); - return $current_date->format('Y-m-d\TH:i:s.uP'); - } - - /** - * Replace `.` with `&46;` in keys of a $statement. - * @param Statement $statement - * @return Statement - */ - private function replaceFullStop(array $statement){ - return \Locker\Helpers\Helpers::replaceFullStop($statement); - } - - /** - * Check to see if a submitted statementId already exist and if so - * are the two statements idntical? If not, return true. - * - * @param uuid $id - * @param string $lrs - * @return boolean - * - **/ - private function doesStatementIdExist($lrsId, array $statement) { - $existingModel = $this->statement - ->where('lrs._id', $lrsId) - ->where('statement.id', $statement['id']) - ->first(); - - if ($existingModel) { - $existingStatement = json_encode($existingModel->statement); - $this->checkMatch($existingStatement, json_encode($statement)); - return $existingModel; - } - - return null; - } - - public function getAttachments($statements, $lrs) { - $destination_path = Helpers::getEnvVar('LOCAL_FILESTORE').'/'.$lrs.'/attachments/'; - $attachments = []; - - foreach ($statements as $statement) { - $statement = $statement['statement']; - $attachments = array_merge($attachments, array_map(function ($attachment) use ($destination_path) { - $ext = array_search($attachment['contentType'], FileTypes::getMap()); - $filename = $attachment['sha2'].'.'.$ext; - return ( - 'Content-Type:'.$attachment['contentType']."\r\n". - 'Content-Transfer-Encoding:binary'."\r\n". - 'X-Experience-API-Hash:'.$attachment['sha2']. - "\r\n\r\n". - file_get_contents($destination_path.$filename) - ); - }, isset($statement['attachments']) ? $statement['attachments'] : [])); - } - - return $attachments; - } - - /** - * Store any attachments - * - **/ - private function storeAttachments( $attachments, $lrs ){ - foreach ($attachments as $attachment) { - // Determines the delimiter. - $delim = "\n"; - if (strpos($attachment, "\r".$delim) !== false) $delim = "\r".$delim; - - // Separate body contents from headers - $attachment = ltrim($attachment, $delim); - list($raw_headers, $body) = explode($delim.$delim, $attachment, 2); - - // Parse headers and separate so we can access - $raw_headers = explode($delim, $raw_headers); - $headers = []; - foreach ($raw_headers as $header) { - list($name, $value) = explode(':', $header); - $headers[strtolower($name)] = ltrim($value, ' '); - } - - //get the correct ext if valid - $ext = array_search($headers['content-type'], FileTypes::getMap()); - if ($ext === false) throw new Exceptions\Exception( - 'This file type cannot be supported' - ); - - $destination_path = Helpers::getEnvVar('LOCAL_FILESTORE').'/'.$lrs.'/attachments/'; - $filename = $headers['x-experience-api-hash']; - - //create directory if it doesn't exist - if (!\File::exists($destination_path)) { - \File::makeDirectory($destination_path, 0775, true); - } - - $filename = $destination_path.$filename.'.'.$ext; - $file = fopen($filename, 'wb'); //opens the file for writing with a BINARY (b) fla - $size = fwrite($file, $body); //write the data to the file - fclose($file); - - if($size === false) throw new Exceptions\Exception( - 'There was an issue saving the attachment' - ); - } - - } - - /** - * Count statements for any give lrs - * @param string Lrs - * @param array parameters Any parameters for filtering - * @return count - **/ - public function count( $lrs, $parameters=null ){ - $query = $this->statement->where('lrs._id', $lrs); - if(!is_null($parameters)){ - $this->addParameters( $query, $parameters, true ); - } - $count = $query->count(); - $query->remember(5); - return $count; - } - - public function grouped($id, $parameters){ - $type = isset($parameters['grouping']) ? strtolower($parameters['grouping']) : ''; - - switch ($type) { - case "time": - $interval = isset($parameters['interval']) ? $parameters['interval'] : "day"; - $filters = isset($parameters['filters']) ? json_decode($parameters['filters'], true) : array(); - $filters['lrs._id'] = $id; - $results = $this->query->timedGrouping( $filters, $interval ); - break; - } - - return $results; - } - - public function timeGrouping($query, $interval ){ - return $query; - } - - public function actorGrouping($query){ - $query->aggregate( - array('$match' => array()), - array( - '$group' => array( - '_id' => 'statement.actor.mbox' - ) - ) - ); - return $query; - } -} diff --git a/app/locker/repository/Statement/EloquentStorer.php b/app/locker/repository/Statement/EloquentStorer.php new file mode 100644 index 0000000000..54ed2a7838 --- /dev/null +++ b/app/locker/repository/Statement/EloquentStorer.php @@ -0,0 +1,90 @@ +inserter = new EloquentInserter(); + $this->linker = new EloquentLinker(); + $this->voider = new EloquentVoider(); + $this->attacher = new FileAttacher(); + } + + /** + * Stores statements and attachments with the given options + * @param [\stdClass] $statements + * @param [String => Mixed] $attachments + * @param StoreOptions $opts + * @return [String] UUIDs of the statements stored. + */ + public function store(array $statements, array $attachments, StoreOptions $opts) { + $id_statements = $this->constructValidStatements($statements, $opts); + $ids = array_keys($statements); + $statements = array_values($id_statements); + + $this->inserter->insert($statements, $opts); + $this->linker->updateReferences($statements, $opts); + $this->voider->voidStatements($statements, $opts); + + $this->activateStatements($ids, $opts); + $this->attacher->store($attachments, $opts); + + return $ids; + } + + /** + * Constructs valid statements. + * @param [\stdClass] $statements + * @param StoreOptions $opts + * @return [String => \stdClass] Array of statements mapped to their UUIDs. + */ + private function constructValidStatements(array $statements, StoreOptions $opts) { + $constructed = []; + + foreach ($statements as $statement) { + $statement->authority = $opts->getOpt('authority'); + $statement->stored = Helpers::getCurrentDate(); + + if (!isset($statement->timestamp)) { + $statement->timestamp = $statement->stored; + } + + if (!isset($statement->id)) { + $statement->id = Helpers::makeUUID(); + } + + // Validates statement. + $constructed_statement = new XAPIStatement($statement); + Helpers::validateAtom($constructed_statement, 'statement'); + $statement = $constructed_statement->toValue(); + + // Adds $statement to $constructed. + if (isset($constructed[$statement->id])) { + $this->inserter->compareForConflict($statement, $constructed[$statement->id]); + } else { + $constructed[$statement->id] = $statement; + } + } + + return $constructed; + } + + /** + * Activates the statements using their UUIDs. + * @param [String] $ids UUIDs of the statements to be activated. + * @param StoreOptions $opts + */ + private function activateStatements(array $ids, StoreOptions $opts) { + return $this->where($opts) + ->whereIn('statement.id', $ids) + ->update(['active' => true]); + } +} diff --git a/app/locker/repository/Statement/EloquentVoider.php b/app/locker/repository/Statement/EloquentVoider.php index 18190fd781..de89dac5cb 100644 --- a/app/locker/repository/Statement/EloquentVoider.php +++ b/app/locker/repository/Statement/EloquentVoider.php @@ -3,7 +3,7 @@ use \Illuminate\Database\Eloquent\Model as Model; interface VoiderInterface { - public function voidStatements(array $statements, array $opts); + public function voidStatements(array $statements, StoreOptions $opts); } class EloquentVoider extends EloquentLinker implements VoiderInterface { @@ -11,9 +11,9 @@ class EloquentVoider extends EloquentLinker implements VoiderInterface { /** * Voids statements that need to be voided. * @param [\stdClass] $statements - * @param [String => Mixed] $opts + * @param StoreOptions $opts */ - public function voidStatements(array $statements, array $opts) { + public function voidStatements(array $statements, StoreOptions $opts) { return array_map(function (\stdClass $voider) use ($opts) { return $this->voidStatement($voider, $opts); }, $statements); @@ -22,9 +22,9 @@ public function voidStatements(array $statements, array $opts) { /** * Voids a statement if it needs to be voided. * @param \stdClass $voider - * @param [String => Mixed] $opts + * @param StoreOptions $opts */ - private function voidStatement(\stdClass $voider, array $opts) { + private function voidStatement(\stdClass $voider, StoreOptions $opts) { if (!$this->isVoiding($voider)) return; $voided = $this->where($opts) diff --git a/app/locker/repository/Statement/FileAttacher.php b/app/locker/repository/Statement/FileAttacher.php new file mode 100644 index 0000000000..d4e77f62d7 --- /dev/null +++ b/app/locker/repository/Statement/FileAttacher.php @@ -0,0 +1,13 @@ +getOpt('lrs_id').'/attachments/'; + if (!is_dir($dir)) mkdir($dir, null, true); + + foreach ($attachments as $attachment) { + file_put_contents($dir.$attachment->file, $attachment->content); + } + } +} diff --git a/app/locker/repository/Statement/IndexOptions.php b/app/locker/repository/Statement/IndexOptions.php index 20f0bb26f6..c403e43c3f 100644 --- a/app/locker/repository/Statement/IndexOptions.php +++ b/app/locker/repository/Statement/IndexOptions.php @@ -3,42 +3,52 @@ use \Locker\Helpers\Helpers as Helpers; use \Locker\Helpers\Exceptions as Exceptions; -class IndexOptions { - public $options = []; - - public function __construct(array $opts) { - $this->options = $this->mergeDefaults($opts); - $this->validate(); - } - - public function getOpt($opt) { - return $this->options[$opt]; - } +class IndexOptions extends Options { + protected $defaults = [ + 'agent' => null, + 'activity' => null, + 'verb' => null, + 'registration' => null, + 'since' => null, + 'until' => null, + 'active' => true, + 'voided' => false, + 'related_activities' => false, + 'related_agents' => false, + 'ascending' => false, + 'format' => 'exact', + 'offset' => 0, + 'limit' => 0, + 'langs' => [], + 'attachments' => false + ]; + protected $types = [ + 'agent' => 'Agent', + 'activity' => 'IRI', + 'verb' => 'IRI', + 'registration' => 'UUID', + 'since' => 'Timestamp', + 'until' => 'Timestamp', + 'active' => 'Boolean', + 'voided' => 'Boolean', + 'related_activities' => 'Boolean', + 'related_agents' => 'Boolean', + 'ascending' => 'Boolean', + 'format' => 'String', + 'offset' => 'Integer', + 'limit' => 'Integer', + 'langs' => 'Collection', + 'attachments' => 'Boolean', + 'lrs_id' => 'String' + ]; /** * Validates the given options as index options. + * @param [String => Mixed] $opts + * @return [String => Mixed] */ - private function validate() { - $opts = $this->options; - - // Validates types. - $this->validateOpt($opts, 'agent', 'Agent'); - $this->validateOpt($opts, 'activity', 'IRI'); - $this->validateOpt($opts, 'verb', 'IRI'); - $this->validateOpt($opts, 'registration', 'UUID'); - $this->validateOpt($opts, 'since', 'Timestamp'); - $this->validateOpt($opts, 'until', 'Timestamp'); - $this->validateOpt($opts, 'active', 'Boolean'); - $this->validateOpt($opts, 'voided', 'Boolean'); - $this->validateOpt($opts, 'related_activities', 'Boolean'); - $this->validateOpt($opts, 'related_agents', 'Boolean'); - $this->validateOpt($opts, 'ascending', 'Boolean'); - $this->validateOpt($opts, 'format', 'String'); - $this->validateOpt($opts, 'offset', 'Integer'); - $this->validateOpt($opts, 'limit', 'Integer'); - $this->validateOpt($opts, 'langs', 'Collection'); - $this->validateOpt($opts, 'attachments', 'Boolean'); - $this->validateOpt($opts, 'lrs_id', 'String'); + protected function validate($opts) { + $opts = parent::validate($opts); // Validates values. if (!isset($opts['lrs_id'])) throw new Exceptions\Exception('`lrs_id` must be set.'); @@ -47,13 +57,8 @@ private function validate() { if (!in_array($opts['format'], ['exact', 'canonical', 'ids'])) { throw new Exceptions\Exception('`format` must be "exact", "canonical", or "ids".'); } - } - private function validateOpt($opts, $opt, $type) { - $class = '\Locker\XApi\\'.$type; - if (isset($opts[$opt])) { - Helpers::validateAtom(new $class($opts[$opt])); - } + return $opts; } /** @@ -61,60 +66,21 @@ private function validateOpt($opts, $opt, $type) { * @param [String => mixed] $opts Index options. * @return [String => mixed] */ - private function mergeDefaults(array $opts) { + protected function mergeDefaults(array $opts) { // Merges with defaults. - $options = array_merge([ - 'agent' => null, - 'activity' => null, - 'verb' => null, - 'registration' => null, - 'since' => null, - 'until' => null, - 'active' => true, - 'voided' => false, - 'related_activities' => false, - 'related_agents' => false, - 'ascending' => false, - 'format' => 'exact', - 'offset' => 0, - 'limit' => 0, - 'langs' => [], - 'attachments' => false - ], $opts); + $opts = parent::mergeDefaults($opts); // Converts types. - $options['active'] = $this->convertToBoolean($options['active']); - $options['voided'] = $this->convertToBoolean($options['voided']); - $options['related_agents'] = $this->convertToBoolean($options['related_agents']); - $options['related_activities'] = $this->convertToBoolean($options['related_activities']); - $options['attachments'] = $this->convertToBoolean($options['attachments']); - $options['ascending'] = $this->convertToBoolean($options['ascending']); - $options['limit'] = $this->convertToInt($options['limit']); - $options['offset'] = $this->convertToInt($options['offset']); + $opts['active'] = $this->convertToBoolean($opts['active']); + $opts['voided'] = $this->convertToBoolean($opts['voided']); + $opts['related_agents'] = $this->convertToBoolean($opts['related_agents']); + $opts['related_activities'] = $this->convertToBoolean($opts['related_activities']); + $opts['attachments'] = $this->convertToBoolean($opts['attachments']); + $opts['ascending'] = $this->convertToBoolean($opts['ascending']); + $opts['limit'] = $this->convertToInt($opts['limit']); + $opts['offset'] = $this->convertToInt($opts['offset']); - if ($options['limit'] === 0) $options['limit'] = 100; - return $options; - } - - /** - * Converts the given value to a Boolean if it can be. - * @param mixed $value - * @return Boolean|mixed Returns the value unchanged if it can't be converted. - */ - private function convertToBoolean($value) { - if (is_string($value)) $value = strtolower($value); - if ($value === 'true') return true; - if ($value === 'false') return false; - return $value; - } - - /** - * Converts the given value to a Integer if it can be. - * @param mixed $value - * @return Integer|mixed Returns the value unchanged if it can't be converted. - */ - private function convertToInt($value) { - $converted_value = (int) $value; - return ($value !== (string) $converted_value) ? $value : $converted_value; + if ($opts['limit'] === 0) $opts['limit'] = 100; + return $opts; } } diff --git a/app/locker/repository/Statement/Options.php b/app/locker/repository/Statement/Options.php new file mode 100644 index 0000000000..37863e3fd0 --- /dev/null +++ b/app/locker/repository/Statement/Options.php @@ -0,0 +1,71 @@ +options = $this->mergeDefaults($opts); + $this->options = $this->validate($this->options); + } + + /** + * Gets an options. + * @param String $opt Option name. + * @return Mixed + */ + public function getOpt($opt) { + return $this->options[$opt]; + } + + /** + * Validates the given options as index options. + * @param [String => Mixed] $opts + * @return [String => Mixed] + */ + protected function validate($opts) { + foreach ($opts as $key => $value) { + if ($value !== null) { + $class = '\Locker\XApi\\'.$this->types[$key]; + Helpers::validateAtom(new $class($value)); + } + } + + return $opts; + } + + /** + * Returns all of the index options set to their default or given value (using the given options). + * @param [String => mixed] $opts Index options. + * @return [String => mixed] + */ + protected function mergeDefaults(array $opts) { + return array_merge($this->defaults, $opts); + } + + /** + * Converts the given value to a Boolean if it can be. + * @param mixed $value + * @return Boolean|mixed Returns the value unchanged if it can't be converted. + */ + protected function convertToBoolean($value) { + if (is_string($value)) $value = strtolower($value); + if ($value === 'true') return true; + if ($value === 'false') return false; + return $value; + } + + /** + * Converts the given value to a Integer if it can be. + * @param mixed $value + * @return Integer|mixed Returns the value unchanged if it can't be converted. + */ + protected function convertToInt($value) { + $converted_value = (int) $value; + return ($value !== (string) $converted_value) ? $value : $converted_value; + } +} diff --git a/app/locker/repository/Statement/ShowOptions.php b/app/locker/repository/Statement/ShowOptions.php new file mode 100644 index 0000000000..04e78a374a --- /dev/null +++ b/app/locker/repository/Statement/ShowOptions.php @@ -0,0 +1,13 @@ + false, + 'active' => true + ]; + protected $types = [ + 'lrs_id' => 'String', + 'voided' => 'Boolean', + 'active' => 'Boolean' + ]; +} diff --git a/app/locker/repository/Statement/StatementRepository.php b/app/locker/repository/Statement/StatementRepository.php deleted file mode 100644 index 548b0fe7ed..0000000000 --- a/app/locker/repository/Statement/StatementRepository.php +++ /dev/null @@ -1,12 +0,0 @@ - 'String', + 'authority' => 'Authority' + ]; +} diff --git a/app/tests/Repos/Statement/EloquentIndexerTest.php b/app/tests/Repos/Statement/EloquentIndexerTest.php index bd2e019453..e286dbed71 100644 --- a/app/tests/Repos/Statement/EloquentIndexerTest.php +++ b/app/tests/Repos/Statement/EloquentIndexerTest.php @@ -1,55 +1,13 @@ -indexer = new Indexer(); - $this->lrs = $this->createLRS(); - $this->statements = [$this->createStatement(0)]; - } - - public function createApplication() { - $unitTesting = true; - $testEnvironment = 'testing'; - return require __DIR__ . '/../../../../bootstrap/start.php'; - } - - private function createLrs() { - $lrs = new \Lrs([ - 'title' => 'TestLRS', - 'api' => [], - 'owner' => [], - 'users' => [], - 'domain' => '', - ]); - - $lrs->save(); - return $lrs; - } - - protected function createStatement($id) { - $model = new \Statement($this->getStatement()); - $model->statement->id = ((string) $id).'0000000-0000-0000-0000-000000000000'; - $model->save(); - return $model; - } - - private function getStatement() { - return [ - 'statement' => json_decode(file_get_contents(__DIR__ . '../../../Fixtures/statement.json')), - 'active' => true, - 'voided' => false, - 'refs' => [], - 'timestamp' => new \MongoDate(strtotime('now')), - 'lrs' => [ - '_id' => $this->lrs->_id - ] - ]; } public function testIndex() { @@ -92,12 +50,4 @@ public function testCount() { $this->assertEquals(true, is_int($result)); $this->assertEquals(count($this->statements), $result); } - - public function tearDown() { - $this->lrs->delete(); - foreach ($this->statements as $statement) { - $statement->delete(); - } - parent::tearDown(); - } } diff --git a/app/tests/Repos/Statement/EloquentShowerTest.php b/app/tests/Repos/Statement/EloquentShowerTest.php new file mode 100644 index 0000000000..911acaff98 --- /dev/null +++ b/app/tests/Repos/Statement/EloquentShowerTest.php @@ -0,0 +1,75 @@ +shower = new Shower(); + } + + public function testShow() { + $opts = new ShowOptions([ + 'lrs_id' => $this->lrs->_id + ]); + $result = $this->shower->show('00000000-0000-0000-0000-000000000000', $opts); + + $this->assertEquals(true, is_object($result)); + $this->assertEquals('stdClass', get_class($result)); + $this->assertStatementMatch($this->statements[0]->statement, $result); + } + + public function testShowInactive() { + $opts = new ShowOptions([ + 'lrs_id' => $this->lrs->_id, + 'active' => false + ]); + $model = $this->createStatement(1); + $model->active = false; + $model->save(); + $result = $this->shower->show('10000000-0000-0000-0000-000000000000', $opts); + + $this->assertEquals(true, is_object($result)); + $this->assertEquals('stdClass', get_class($result)); + $this->assertStatementMatch($model->statement, $result); + } + + public function testShowVoided() { + $opts = new ShowOptions([ + 'lrs_id' => $this->lrs->_id, + 'voided' => true + ]); + $model = $this->createStatement(1); + $model->voided = true; + $model->save(); + $result = $this->shower->show('10000000-0000-0000-0000-000000000000', $opts); + + $this->assertEquals(true, is_object($result)); + $this->assertEquals('stdClass', get_class($result)); + $this->assertStatementMatch($model->statement, $result); + } + + public function testShowInactiveVoided() { + $opts = new ShowOptions([ + 'lrs_id' => $this->lrs->_id, + 'active' => false, + 'voided' => true + ]); + $model = $this->createStatement(1); + $model->active = false; + $model->voided = true; + $model->save(); + $result = $this->shower->show('10000000-0000-0000-0000-000000000000', $opts); + + $this->assertEquals(true, is_object($result)); + $this->assertEquals('stdClass', get_class($result)); + $this->assertStatementMatch($model->statement, $result); + } + + private function assertStatementMatch(\stdClass $statement_a, \stdClass $statement_b) { + unset($statement_b->version); + $this->assertEquals(true, $statement_a == $statement_b); + } +} diff --git a/app/tests/Repos/Statement/EloquentTest.php b/app/tests/Repos/Statement/EloquentTest.php new file mode 100644 index 0000000000..5d19298252 --- /dev/null +++ b/app/tests/Repos/Statement/EloquentTest.php @@ -0,0 +1,59 @@ +lrs = $this->createLRS(); + $this->statements = [$this->createStatement(0)]; + } + + public function createApplication() { + $unitTesting = true; + $testEnvironment = 'testing'; + return require __DIR__ . '/../../../../bootstrap/start.php'; + } + + private function createLrs() { + $lrs = new \Lrs([ + 'title' => 'TestLRS', + 'api' => [], + 'owner' => [], + 'users' => [], + 'domain' => '', + ]); + + $lrs->save(); + return $lrs; + } + + protected function createStatement($id) { + $model = new \Statement($this->getStatement()); + $model->statement->id = ((string) $id).'0000000-0000-0000-0000-000000000000'; + $model->save(); + return $model; + } + + protected function getStatement() { + return [ + 'statement' => json_decode(file_get_contents(__DIR__ . '../../../Fixtures/statement.json')), + 'active' => true, + 'voided' => false, + 'refs' => [], + 'timestamp' => new \MongoDate(strtotime('now')), + 'lrs' => [ + '_id' => $this->lrs->_id + ] + ]; + } + + public function tearDown() { + $this->lrs->delete(); + foreach ($this->statements as $statement) { + $statement->delete(); + } + parent::tearDown(); + } +} diff --git a/app/tests/Repos/Statement/FormatterTest.php b/app/tests/Repos/Statement/FormatterTest.php index 655148db5e..cd6c1e4402 100644 --- a/app/tests/Repos/Statement/FormatterTest.php +++ b/app/tests/Repos/Statement/FormatterTest.php @@ -1,4 +1,4 @@ - '1', + ]; + protected $default_opts = [ + 'agent' => null, + 'activity' => null, + 'verb' => null, + 'registration' => null, + 'since' => null, + 'until' => null, + 'active' => true, + 'voided' => false, + 'related_activities' => false, + 'related_agents' => false, + 'ascending' => false, + 'format' => 'exact', + 'offset' => 0, + 'limit' => 100, + 'langs' => [], + 'attachments' => false, + 'lrs_id' => '1' + ]; + protected $valid_opts = [ + 'activity' => 'http://www.example.com', + 'verb' => 'http://www.example.com', + 'registration' => '93439880-e35a-11e4-b571-0800200c9a66', + 'since' => '2015-01-01T00:00Z', + 'until' => '2015-01-01T00:00Z', + 'active' => true, + 'voided' => true, + 'related_activities' => true, + 'related_agents' => true, + 'ascending' => true, + 'format' => 'exact', + 'offset' => 0, + 'limit' => 1, + 'langs' => [], + 'attachments' => true, + 'lrs_id' => '1' + ]; + protected $invalid_opts = [ + 'activity' => 'zz.example.com', + 'verb' => 'zz.example.com', + 'registration' => '93439880-e35a-11e4-b571-0800200c9a66Z', + 'since' => '2015-01-01T00:00ZYX', + 'until' => '2015-01-01T00:00ZYX', + 'active' => 'invalid', + 'voided' => 'invalid', + 'related_activities' => 'invalid', + 'related_agents' => 'invalid', + 'ascending' => 'invalid', + 'format' => 'invalid', + 'offset' => -1, + 'limit' => -1, + 'langs' => 'invalid', + 'attachments' => 'invalid', + 'lrs_id' => true + ]; public function setup() { parent::setup(); - $this->default_opts = [ - 'agent' => null, - 'activity' => null, - 'verb' => null, - 'registration' => null, - 'since' => null, - 'until' => null, - 'active' => true, - 'voided' => false, - 'related_activities' => false, - 'related_agents' => false, - 'ascending' => false, - 'format' => 'exact', - 'offset' => 0, - 'limit' => 100, - 'langs' => [], - 'attachments' => false, - 'lrs_id' => '1' - ]; - $this->test_opts = [ - 'agent' => (object) ['mbox' => 'mailto:test@example.com'], - 'activity' => 'http://www.example.com', - 'verb' => 'http://www.example.com', - 'registration' => '93439880-e35a-11e4-b571-0800200c9a66', - 'since' => '2015-01-01T00:00Z', - 'until' => '2015-01-01T00:00Z', - 'active' => true, - 'voided' => true, - 'related_activities' => true, - 'related_agents' => true, - 'ascending' => true, - 'format' => 'exact', - 'offset' => 0, - 'limit' => 1, - 'langs' => [], - 'attachments' => true, - 'lrs_id' => '1' - ]; - $this->invalid_opts = [ - 'agent' => (object) ['mbox' => 'test@example.com'], - 'activity' => 'zz.example.com', - 'verb' => 'zz.example.com', - 'registration' => '93439880-e35a-11e4-b571-0800200c9a66Z', - 'since' => '2015-01-01T00:00ZYX', - 'until' => '2015-01-01T00:00ZYX', - 'active' => 'invalid', - 'voided' => 'invalid', - 'related_activities' => 'invalid', - 'related_agents' => 'invalid', - 'ascending' => 'invalid', - 'format' => 'invalid', - 'offset' => -1, - 'limit' => -1, - 'langs' => 'invalid', - 'attachments' => 'invalid', - 'lrs_id' => null - ]; - } - - public function createApplication() { - $unitTesting = true; - $testEnvironment = 'testing'; - return require __DIR__ . '/../../../../bootstrap/start.php'; - } - - public function testValidOptions() { - try { - $start_opts = $this->test_opts; - $end_opts = new IndexOptions($start_opts); - - foreach ($end_opts->options as $key => $val) { - $this->assertEquals($start_opts[$key], $val); - } - } catch (\Exception $ex) { - $this->assertEquals(false, true, $ex->getMessage()); - } + $this->valid_opts['agent'] = (object) ['mbox' => 'mailto:test@example.com']; + $this->invalid_opts['agent'] = (object) ['mbox' => 'test@example.com']; } public function testValidCanonicalFormat() { try { - $start_opts = $this->test_opts; + $start_opts = $this->valid_opts; $start_opts['format'] = 'canonical'; - $end_opts = new IndexOptions($start_opts); + $end_opts = new $this->options_class($start_opts); foreach ($end_opts->options as $key => $val) { $this->assertEquals($start_opts[$key], $val); } } catch (\Exception $ex) { + \Log::error($ex); $this->assertEquals(false, true, $ex->getMessage()); } } public function testValidIdsFormat() { try { - $start_opts = $this->test_opts; + $start_opts = $this->valid_opts; $start_opts['format'] = 'ids'; - $end_opts = new IndexOptions($start_opts); - - foreach ($end_opts->options as $key => $val) { - $this->assertEquals($start_opts[$key], $val); - } - } catch (\Exception $ex) { - $this->assertEquals(false, true, $ex->getMessage()); - } - } - - public function testDefaultOptions() { - try { - $start_opts = $this->default_opts; - $end_opts = new IndexOptions(['lrs_id' => $this->default_opts['lrs_id']]); + $end_opts = new $this->options_class($start_opts); foreach ($end_opts->options as $key => $val) { $this->assertEquals($start_opts[$key], $val); } } catch (\Exception $ex) { - $this->assertEquals(false, true, $ex->getMessage()); - } - } - - public function testInvalidOptions() { - foreach ($this->invalid_opts as $invalid_key => $invalid_val) { - try { - $caught = false; - $start_opts = $this->test_opts; - $start_opts[$invalid_key] = $invalid_val; - $end_opts = new IndexOptions($start_opts); - } catch (\Exception $ex) { - $caught = true; - } - $this->assertEquals(true, $caught); - } - } - - public function testGetOpts() { - try { - $start_opts = $this->test_opts; - $end_opts = new IndexOptions($start_opts); - - foreach ($start_opts as $key => $val) { - $this->assertEquals($val, $end_opts->getOpt($key)); - } - } catch (\Exception $ex) { + \Log::error($ex); $this->assertEquals(false, true, $ex->getMessage()); } } diff --git a/app/tests/Repos/Statement/OptionsTest.php b/app/tests/Repos/Statement/OptionsTest.php new file mode 100644 index 0000000000..95dd75f6d9 --- /dev/null +++ b/app/tests/Repos/Statement/OptionsTest.php @@ -0,0 +1,74 @@ +valid_opts; + $end_opts = new $this->options_class($start_opts); + + foreach ($end_opts->options as $key => $val) { + $this->assertEquals($start_opts[$key], $val); + } + } catch (\Exception $ex) { + \Log::error($ex); + $this->assertEquals(false, true, $ex->getMessage()); + } + } + + public function testDefaultOptions() { + try { + $start_opts = $this->default_opts; + $end_opts = new $this->options_class($this->overwrite_opts); + + foreach ($end_opts->options as $key => $val) { + $this->assertEquals($start_opts[$key], $val); + } + } catch (\Exception $ex) { + \Log::error($ex); + $this->assertEquals(false, true, $ex->getMessage()); + } + } + + public function testInvalidOptions() { + foreach ($this->invalid_opts as $invalid_key => $invalid_val) { + try { + $caught = false; + $start_opts = $this->valid_opts; + $start_opts[$invalid_key] = $invalid_val; + $end_opts = new $this->options_class($start_opts); + } catch (\Exception $ex) { + $caught = true; + } + $this->assertEquals(true, $caught); + } + } + + public function testGetOpts() { + try { + $start_opts = $this->valid_opts; + $end_opts = new $this->options_class($start_opts); + + foreach ($start_opts as $key => $val) { + $this->assertEquals($val, $end_opts->getOpt($key)); + } + } catch (\Exception $ex) { + \Log::error($ex); + $this->assertEquals(false, true, $ex->getMessage()); + } + } + +} diff --git a/app/tests/Repos/Statement/ShowOptionsTest.php b/app/tests/Repos/Statement/ShowOptionsTest.php new file mode 100644 index 0000000000..67cd536095 --- /dev/null +++ b/app/tests/Repos/Statement/ShowOptionsTest.php @@ -0,0 +1,24 @@ + '1', + ]; + protected $default_opts = [ + 'lrs_id' => '1', + 'voided' => false, + 'active' => true + ]; + protected $valid_opts = [ + 'lrs_id' => '1', + 'voided' => true, + 'active' => false + ]; + protected $invalid_opts = [ + 'lrs_id' => true, + 'voided' => 'false', + 'active' => 'true' + ]; + +} diff --git a/app/tests/Repos/Statement/StoreOptionsTest.php b/app/tests/Repos/Statement/StoreOptionsTest.php new file mode 100644 index 0000000000..623c8a0069 --- /dev/null +++ b/app/tests/Repos/Statement/StoreOptionsTest.php @@ -0,0 +1,26 @@ + '1', + ]; + protected $default_opts = [ + 'lrs_id' => '1', + ]; + protected $valid_opts = [ + 'lrs_id' => '1', + ]; + protected $invalid_opts = [ + 'lrs_id' => true + ]; + + public function setup() { + parent::setup(); + $this->overwrite_opts['authority'] = (object) ['mbox' => 'mailto:test@example.com']; + $this->default_opts['authority'] = $this->overwrite_opts['authority']; + $this->valid_opts['authority'] = $this->overwrite_opts['authority']; + $this->invalid_opts['authority'] = (object) ['mbox' => 'test@example.com']; + } + +} From fbd760d0b0fd7ad62fdfc232e51334667190da9c Mon Sep 17 00:00:00 2001 From: Ryan Smith <0ryansmith1994@gmail.com> Date: Mon, 20 Apr 2015 10:03:38 +0100 Subject: [PATCH 14/34] Removes testing covered by conformance tests. --- .../Member/object-type-is-not-agent.json | 26 ------ .../Invalid/Actor/Group/missing-member.json | 16 ---- .../Invalid/Actor/Mbox/invalid-format.json | 16 ---- .../Invalid/Actor/missing-actor.json | 19 ----- .../content-type-is-not-string.json | 25 ------ .../Invalid/Attachment/display.json | 27 ------- .../Attachment/length-is-not-integer.json | 25 ------ .../Invalid/Attachment/missing-sha2.json | 24 ------ .../Attachment/missing-usage-type.json | 24 ------ .../Invalid/Attachment/sha2-is-not-valid.json | 25 ------ .../Authority/Member/wrong-object-type.json | 29 ------- .../Invalid/Verb/missing-display.json | 12 --- .../Statements/Valid/Actor/group.json | 26 ------ .../Valid/Object/with-definition.json | 24 ------ .../Valid/Verb/Display/multilingual.json | 18 ----- .../Fixtures/Statements/Valid/simple.json | 16 ---- .../Statements/Valid/with-authority.json | 21 ----- .../Statements/Valid/with-context.json | 48 ----------- .../BaseStatementValidationTest.php | 34 -------- .../StatementValidationActorTest.php | 48 ----------- .../StatementValidationAttachmentTest.php | 80 ------------------- .../StatementValidationAuthorityTest.php | 15 ---- .../StatementValidationObjectTest.php | 6 -- .../Validation/StatementValidationTest.php | 43 ---------- 24 files changed, 647 deletions(-) delete mode 100644 app/tests/Fixtures/Statements/Invalid/Actor/Group/Member/object-type-is-not-agent.json delete mode 100644 app/tests/Fixtures/Statements/Invalid/Actor/Group/missing-member.json delete mode 100644 app/tests/Fixtures/Statements/Invalid/Actor/Mbox/invalid-format.json delete mode 100644 app/tests/Fixtures/Statements/Invalid/Actor/missing-actor.json delete mode 100644 app/tests/Fixtures/Statements/Invalid/Attachment/content-type-is-not-string.json delete mode 100644 app/tests/Fixtures/Statements/Invalid/Attachment/display.json delete mode 100644 app/tests/Fixtures/Statements/Invalid/Attachment/length-is-not-integer.json delete mode 100644 app/tests/Fixtures/Statements/Invalid/Attachment/missing-sha2.json delete mode 100644 app/tests/Fixtures/Statements/Invalid/Attachment/missing-usage-type.json delete mode 100644 app/tests/Fixtures/Statements/Invalid/Attachment/sha2-is-not-valid.json delete mode 100644 app/tests/Fixtures/Statements/Invalid/Authority/Member/wrong-object-type.json delete mode 100644 app/tests/Fixtures/Statements/Invalid/Verb/missing-display.json delete mode 100644 app/tests/Fixtures/Statements/Valid/Actor/group.json delete mode 100644 app/tests/Fixtures/Statements/Valid/Object/with-definition.json delete mode 100644 app/tests/Fixtures/Statements/Valid/Verb/Display/multilingual.json delete mode 100644 app/tests/Fixtures/Statements/Valid/simple.json delete mode 100644 app/tests/Fixtures/Statements/Valid/with-authority.json delete mode 100644 app/tests/Fixtures/Statements/Valid/with-context.json delete mode 100644 app/tests/xAPI/Statement/Validation/BaseStatementValidationTest.php delete mode 100644 app/tests/xAPI/Statement/Validation/StatementValidationActorTest.php delete mode 100644 app/tests/xAPI/Statement/Validation/StatementValidationAttachmentTest.php delete mode 100644 app/tests/xAPI/Statement/Validation/StatementValidationAuthorityTest.php delete mode 100644 app/tests/xAPI/Statement/Validation/StatementValidationObjectTest.php delete mode 100644 app/tests/xAPI/Statement/Validation/StatementValidationTest.php diff --git a/app/tests/Fixtures/Statements/Invalid/Actor/Group/Member/object-type-is-not-agent.json b/app/tests/Fixtures/Statements/Invalid/Actor/Group/Member/object-type-is-not-agent.json deleted file mode 100644 index b75a1b373c..0000000000 --- a/app/tests/Fixtures/Statements/Invalid/Actor/Group/Member/object-type-is-not-agent.json +++ /dev/null @@ -1,26 +0,0 @@ -{ - "id": "88664422-1234-5678-1234-567812345678", - "actor": { - "objectType": "Group", - "member": [ - { - "objectType": "Agent", - "mbox": "mailto:bob@example.com" - }, - { - "objectType": "Group", - "mbox": "mailto:group@example.com" - } - ] - }, - "verb": { - "id": "http://adlnet.gov/expapi/verbs/created", - "display": { - "en-US": "created" - } - }, - "object": { - "id": "http://ZackPierce.github.io/xAPI-Validator-JS", - "objectType": "Activity" - } -} \ No newline at end of file diff --git a/app/tests/Fixtures/Statements/Invalid/Actor/Group/missing-member.json b/app/tests/Fixtures/Statements/Invalid/Actor/Group/missing-member.json deleted file mode 100644 index 4a6c28c42c..0000000000 --- a/app/tests/Fixtures/Statements/Invalid/Actor/Group/missing-member.json +++ /dev/null @@ -1,16 +0,0 @@ -{ - "id": "88664422-1234-5678-1234-567812345678", - "actor": { - "objectType": "Group" - }, - "verb": { - "id": "http://adlnet.gov/expapi/verbs/created", - "display": { - "en-US": "created" - } - }, - "object": { - "id": "http://ZackPierce.github.io/xAPI-Validator-JS", - "objectType": "Activity" - } -} \ No newline at end of file diff --git a/app/tests/Fixtures/Statements/Invalid/Actor/Mbox/invalid-format.json b/app/tests/Fixtures/Statements/Invalid/Actor/Mbox/invalid-format.json deleted file mode 100644 index 0a922804dc..0000000000 --- a/app/tests/Fixtures/Statements/Invalid/Actor/Mbox/invalid-format.json +++ /dev/null @@ -1,16 +0,0 @@ -{ - "id": "88664422-1234-5678-1234-567812345678", - "actor": { - "mbox": "A@b@c@example.com" - }, - "verb": { - "id": "http://adlnet.gov/expapi/verbs/created", - "display": { - "en-US": "created" - } - }, - "object": { - "id": "http://ZackPierce.github.io/xAPI-Validator-JS", - "objectType": "Activity" - } -} \ No newline at end of file diff --git a/app/tests/Fixtures/Statements/Invalid/Actor/missing-actor.json b/app/tests/Fixtures/Statements/Invalid/Actor/missing-actor.json deleted file mode 100644 index 950d2d9831..0000000000 --- a/app/tests/Fixtures/Statements/Invalid/Actor/missing-actor.json +++ /dev/null @@ -1,19 +0,0 @@ -{ - "id": "54321876-b88b-4b20-a0a5-a4c32391aaa0", - "verb": { - "id": "http://adlnet.gov/expapi/verbs/created", - "display": { - "en-US": "created" - } - }, - "object": { - "id": "http://ZackPierce.github.io/xAPI-Validator-JS", - "objectType": "Activity" - }, - "verb": { - "id": "http:\/\/adlnet.gov\/expapi\/verbs\/experienced", - "display": { - "und": "experienced" - } - } -} \ No newline at end of file diff --git a/app/tests/Fixtures/Statements/Invalid/Attachment/content-type-is-not-string.json b/app/tests/Fixtures/Statements/Invalid/Attachment/content-type-is-not-string.json deleted file mode 100644 index d8b5605139..0000000000 --- a/app/tests/Fixtures/Statements/Invalid/Attachment/content-type-is-not-string.json +++ /dev/null @@ -1,25 +0,0 @@ -{ - "id": "54321876-b88b-4b20-a0a5-a4c32391aaa0", - "actor": { - "mbox": "mailto:zachary+pierce@gmail.com" - }, - "verb": { - "id": "http:\/\/adlnet.gov\/expapi\/verbs\/experienced", - "display": { - "und": "experienced" - } - }, - "object": { - "id": "http://ZackPierce.github.io/xAPI-Validator-JS", - "objectType": "Activity" - }, - "attachments": [ - { - "usageType": "http://example.com/usage/info/A", - "display": {"en-GB":"hello world"}, - "contentType": 1.23, - "length": 654, - "sha2": "71b9be12fb30c4648b5f17b37d705440c78bbec56b4e30a40deeb5f163e617f2" - } - ] -} diff --git a/app/tests/Fixtures/Statements/Invalid/Attachment/display.json b/app/tests/Fixtures/Statements/Invalid/Attachment/display.json deleted file mode 100644 index 67bf027986..0000000000 --- a/app/tests/Fixtures/Statements/Invalid/Attachment/display.json +++ /dev/null @@ -1,27 +0,0 @@ -{ - "id": "54321876-b88b-4b20-a0a5-a4c32391aaa0", - "actor": { - "mbox": "mailto:zachary+pierce@gmail.com" - }, - "verb": { - "id": "http:\/\/adlnet.gov\/expapi\/verbs\/experienced", - "display": { - "und": "experienced" - } - }, - "object": { - "id": "http://ZackPierce.github.io/xAPI-Validator-JS", - "objectType": "Activity" - }, - "attachments": [ - { - "usageType": "http://example.com/usage/info/A", - "display": { - "$": "hello" - }, - "contentType": "text/plain", - "length": 100, - "sha2": "71b9be12fb30c4648b5f17b37d705440c78bbec56b4e30a40deeb5f163e617f2" - } - ] -} diff --git a/app/tests/Fixtures/Statements/Invalid/Attachment/length-is-not-integer.json b/app/tests/Fixtures/Statements/Invalid/Attachment/length-is-not-integer.json deleted file mode 100644 index 8f10f365a3..0000000000 --- a/app/tests/Fixtures/Statements/Invalid/Attachment/length-is-not-integer.json +++ /dev/null @@ -1,25 +0,0 @@ -{ - "id": "54321876-b88b-4b20-a0a5-a4c32391aaa0", - "actor": { - "mbox": "mailto:zachary+pierce@gmail.com" - }, - "verb": { - "id": "http:\/\/adlnet.gov\/expapi\/verbs\/experienced", - "display": { - "und": "experienced" - } - }, - "object": { - "id": "http://ZackPierce.github.io/xAPI-Validator-JS", - "objectType": "Activity" - }, - "attachments": [ - { - "usageType": "http://example.com/usage/info/A", - "display": {"en-GB":"hello world"}, - "contentType": "text/plain", - "length": "100MB", - "sha2": "71b9be12fb30c4648b5f17b37d705440c78bbec56b4e30a40deeb5f163e617f2" - } - ] -} diff --git a/app/tests/Fixtures/Statements/Invalid/Attachment/missing-sha2.json b/app/tests/Fixtures/Statements/Invalid/Attachment/missing-sha2.json deleted file mode 100644 index 8a016e9286..0000000000 --- a/app/tests/Fixtures/Statements/Invalid/Attachment/missing-sha2.json +++ /dev/null @@ -1,24 +0,0 @@ -{ - "id": "54321876-b88b-4b20-a0a5-a4c32391aaa0", - "actor": { - "mbox": "mailto:zachary+pierce@gmail.com" - }, - "verb": { - "id": "http:\/\/adlnet.gov\/expapi\/verbs\/experienced", - "display": { - "und": "experienced" - } - }, - "object": { - "id": "http://ZackPierce.github.io/xAPI-Validator-JS", - "objectType": "Activity" - }, - "attachments": [ - { - "usageType": "http://example.com/usage/info/A", - "display": {"en-GB":"hello world"}, - "contentType": "text/plain", - "length": 100 - } - ] -} diff --git a/app/tests/Fixtures/Statements/Invalid/Attachment/missing-usage-type.json b/app/tests/Fixtures/Statements/Invalid/Attachment/missing-usage-type.json deleted file mode 100644 index fea1a0df39..0000000000 --- a/app/tests/Fixtures/Statements/Invalid/Attachment/missing-usage-type.json +++ /dev/null @@ -1,24 +0,0 @@ -{ - "id": "54321876-b88b-4b20-a0a5-a4c32391aaa0", - "actor": { - "mbox": "mailto:zachary+pierce@gmail.com" - }, - "verb": { - "id": "http:\/\/adlnet.gov\/expapi\/verbs\/experienced", - "display": { - "und": "experienced" - } - }, - "object": { - "id": "http://ZackPierce.github.io/xAPI-Validator-JS", - "objectType": "Activity" - }, - "attachments": [ - { - "display": {"en-GB":"hello world"}, - "contentType": "text/plain", - "length": 100, - "sha2": "71b9be12fb30c4648b5f17b37d705440c78bbec56b4e30a40deeb5f163e617f2" - } - ] -} diff --git a/app/tests/Fixtures/Statements/Invalid/Attachment/sha2-is-not-valid.json b/app/tests/Fixtures/Statements/Invalid/Attachment/sha2-is-not-valid.json deleted file mode 100644 index c131f9dc41..0000000000 --- a/app/tests/Fixtures/Statements/Invalid/Attachment/sha2-is-not-valid.json +++ /dev/null @@ -1,25 +0,0 @@ -{ - "id": "54321876-b88b-4b20-a0a5-a4c32391aaa0", - "actor": { - "mbox": "mailto:zachary+pierce@gmail.com" - }, - "verb": { - "id": "http:\/\/adlnet.gov\/expapi\/verbs\/experienced", - "display": { - "und": "experienced" - } - }, - "object": { - "id": "http://ZackPierce.github.io/xAPI-Validator-JS", - "objectType": "Activity" - }, - "attachments": [ - { - "usageType": "http://example.com/usage/info/A", - "display": {"en-GB":"hello world"}, - "contentType": "text/plain", - "length": 100, - "sha2": "invalid-sha2-string" - } - ] -} diff --git a/app/tests/Fixtures/Statements/Invalid/Authority/Member/wrong-object-type.json b/app/tests/Fixtures/Statements/Invalid/Authority/Member/wrong-object-type.json deleted file mode 100644 index 57b6b86785..0000000000 --- a/app/tests/Fixtures/Statements/Invalid/Authority/Member/wrong-object-type.json +++ /dev/null @@ -1,29 +0,0 @@ -{ - "id": "54321876-b88b-4b20-a0a5-a4c32391aaa0", - "actor": { - "mbox": "mailto:zachary+pierce@gmail.com" - }, - "verb": { - "id": "http:\/\/adlnet.gov\/expapi\/verbs\/experienced", - "display": { - "und": "experienced" - } - }, - "object": { - "id": "http://ZackPierce.github.io/xAPI-Validator-JS", - "objectType": "Activity" - }, - "authority": { - "objectType": "Group", - "member": [ - { - "objectType": "Agent", - "mbox": "mailto:bob@example.com" - }, - { - "objectType": "Group", - "mbox": "mailto:group@example.com" - } - ] - } -} diff --git a/app/tests/Fixtures/Statements/Invalid/Verb/missing-display.json b/app/tests/Fixtures/Statements/Invalid/Verb/missing-display.json deleted file mode 100644 index e5195cfe93..0000000000 --- a/app/tests/Fixtures/Statements/Invalid/Verb/missing-display.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "actor": { - "mbox": "mailto:zachary+pierce@gmail.com" - }, - "verb": { - "id": "http:\/\/adlnet.gov\/expapi\/verbs\/experienced" - }, - "object": { - "id": "http://ZackPierce.github.io/xAPI-Validator-JS", - "objectType": "Activity" - } -} diff --git a/app/tests/Fixtures/Statements/Valid/Actor/group.json b/app/tests/Fixtures/Statements/Valid/Actor/group.json deleted file mode 100644 index 77926f0ff1..0000000000 --- a/app/tests/Fixtures/Statements/Valid/Actor/group.json +++ /dev/null @@ -1,26 +0,0 @@ -{ - "id": "88664422-1234-5678-1234-567812345678", - "actor": { - "objectType": "Group", - "member": [ - { - "objectType": "Agent", - "mbox": "mailto:bob@example.com" - }, - { - "objectType": "Agent", - "mbox": "mailto:group@example.com" - } - ] - }, - "verb": { - "id": "http://adlnet.gov/expapi/verbs/created", - "display": { - "en-US": "created" - } - }, - "object": { - "id": "http://ZackPierce.github.io/xAPI-Validator-JS", - "objectType": "Activity" - } -} \ No newline at end of file diff --git a/app/tests/Fixtures/Statements/Valid/Object/with-definition.json b/app/tests/Fixtures/Statements/Valid/Object/with-definition.json deleted file mode 100644 index 7bab3ca69e..0000000000 --- a/app/tests/Fixtures/Statements/Valid/Object/with-definition.json +++ /dev/null @@ -1,24 +0,0 @@ -{ - "id": "12345678-1234-5678-1234-567812345678", - "actor": { - "mbox": "mailto:zachary+pierce@gmail.com" - }, - "verb": { - "id": "http://adlnet.gov/expapi/verbs/created", - "display": { - "en-US": "created" - } - }, - "object": { - "id": "http:\/\/tincanapi.com\/GolfExample_TCAPI\/Playing\/Scoring.html", - "objectType": "Activity", - "definition": { - "name": { - "en-US": "Scoring" - }, - "description": { - "en-US": "An overview of how to score a round of golf." - } - } - } -} diff --git a/app/tests/Fixtures/Statements/Valid/Verb/Display/multilingual.json b/app/tests/Fixtures/Statements/Valid/Verb/Display/multilingual.json deleted file mode 100644 index 947ad1d520..0000000000 --- a/app/tests/Fixtures/Statements/Valid/Verb/Display/multilingual.json +++ /dev/null @@ -1,18 +0,0 @@ -{ - "id": "12345678-1234-5678-1234-567812345678", - "actor": { - "mbox": "mailto:zachary+pierce@gmail.com" - }, - "verb": { - "id": "http://adlnet.gov/expapi/verbs/passed/", - "display": { - "en-US": "created", - "en-US": "created", - "vn": "tạo" - } - }, - "object": { - "id": "http://ZackPierce.github.io/xAPI-Validator-JS", - "objectType": "Activity" - } -} \ No newline at end of file diff --git a/app/tests/Fixtures/Statements/Valid/simple.json b/app/tests/Fixtures/Statements/Valid/simple.json deleted file mode 100644 index 957017d7e7..0000000000 --- a/app/tests/Fixtures/Statements/Valid/simple.json +++ /dev/null @@ -1,16 +0,0 @@ -{ - "id": "12345678-1234-5678-1234-567812345678", - "actor": { - "mbox": "mailto:zachary+pierce@gmail.com" - }, - "verb": { - "id": "http://adlnet.gov/expapi/verbs/created", - "display": { - "en-US": "created" - } - }, - "object": { - "id": "http://ZackPierce.github.io/xAPI-Validator-JS", - "objectType": "Activity" - } -} \ No newline at end of file diff --git a/app/tests/Fixtures/Statements/Valid/with-authority.json b/app/tests/Fixtures/Statements/Valid/with-authority.json deleted file mode 100644 index 8d87e9a0f5..0000000000 --- a/app/tests/Fixtures/Statements/Valid/with-authority.json +++ /dev/null @@ -1,21 +0,0 @@ -{ - "id": "12345678-1234-5678-1234-567812345678", - "actor": { - "mbox": "mailto:zachary+pierce@gmail.com" - }, - "verb": { - "id": "http://adlnet.gov/expapi/verbs/created", - "display": { - "en-US": "created" - } - }, - "object": { - "id": "http://ZackPierce.github.io/xAPI-Validator-JS", - "objectType": "Activity" - }, - "authority": { - "name": "Quan Vo", - "mbox": "mailto:quan@ll.com", - "objectType": "Agent" - } -} \ No newline at end of file diff --git a/app/tests/Fixtures/Statements/Valid/with-context.json b/app/tests/Fixtures/Statements/Valid/with-context.json deleted file mode 100644 index e01466d3a0..0000000000 --- a/app/tests/Fixtures/Statements/Valid/with-context.json +++ /dev/null @@ -1,48 +0,0 @@ -{ - "id": "12345678-1234-5678-1234-567812345678", - "actor": { - "mbox": "mailto:zachary+pierce@gmail.com" - }, - "verb": { - "id": "http://adlnet.gov/expapi/verbs/created", - "display": { - "en-US": "created" - } - }, - "object": { - "id": "http://ZackPierce.github.io/xAPI-Validator-JS", - "objectType": "Activity" - }, - "context": { - "registration": "ec531277-b57b-4c15-8d91-d292c5b2b8f7", - "contextActivities": { - "parent": [ - { - "id": "http://www.example.com/meetings/series/267", - "objectType": "Activity" - } - ], - "category": [ - { - "id": "http://www.example.com/meetings/categories/teammeeting", - "objectType": "Activity", - "definition": { - "name": { - "en": "team meeting" - }, - "description": { - "en": "A category of meeting used for regular team meetings." - }, - "type": "http://example.com/expapi/activities/meetingcategory" - } - } - ], - "other": [ - { - "id": "http://www.example.com/meetings/occurances/3425567", - "objectType": "Activity" - } - ] - } - } -} \ No newline at end of file diff --git a/app/tests/xAPI/Statement/Validation/BaseStatementValidationTest.php b/app/tests/xAPI/Statement/Validation/BaseStatementValidationTest.php deleted file mode 100644 index 812e4f979d..0000000000 --- a/app/tests/xAPI/Statement/Validation/BaseStatementValidationTest.php +++ /dev/null @@ -1,34 +0,0 @@ -json_input = json_decode($json, true); - $auth = isset($this->json_input['authority']) ? $this->json_input['authority'] : [ - 'name' => "John Smith", - 'mbox' => "mailto:test@learninglocker.co.uk", - 'objectType' => "Agent" - ]; - $manager = new xAPIValidation(); - return $manager->runValidation($this->json_input, $auth); - } - -} diff --git a/app/tests/xAPI/Statement/Validation/StatementValidationActorTest.php b/app/tests/xAPI/Statement/Validation/StatementValidationActorTest.php deleted file mode 100644 index 6e8049f58d..0000000000 --- a/app/tests/xAPI/Statement/Validation/StatementValidationActorTest.php +++ /dev/null @@ -1,48 +0,0 @@ -exec($this->getFixturePath() . '/Invalid/Actor/missing-actor.json'); - $this->assertEquals('failed', $results['status']); - $this->assertEquals( - \Lang::get('xAPIValidation.errors.required', array( - 'key' => 'actor', - 'section' => 'core statement' - )), trim($results['errors'][0]) - ); - } - - public function testGroupMissingMember() - { - $results = $this->exec($this->getFixturePath() . '/Invalid/Actor/Group/missing-member.json'); - $this->assertEquals('failed', $results['status']); - $this->assertEquals(\Lang::get('xAPIValidation.errors.required', array( - 'key' => 'member', - 'section' => 'actor' - )), trim($results['errors'][0])); - } - - public function testGroupMemberObjectTypeIsNotAgent() - { - $results = $this->exec($this->getFixturePath() . '/Invalid/Actor/Group/Member/object-type-is-not-agent.json'); - $this->assertEquals('failed', $results['status']); - $this->assertEquals(\Lang::get('xAPIValidation.errors.group.groups'), trim($results['errors'][0])); - } - - public function testMbox() - { - $results = $this->exec($this->getFixturePath() . '/Invalid/Actor/Mbox/invalid-format.json'); - $this->assertEquals('failed', $results['status']); - $this->assertEquals( - \Lang::get('xAPIValidation.errors.format', array( - 'key' => 'mbox', - 'section' => 'actor' - )), trim($results['errors'][0])); - } - -} diff --git a/app/tests/xAPI/Statement/Validation/StatementValidationAttachmentTest.php b/app/tests/xAPI/Statement/Validation/StatementValidationAttachmentTest.php deleted file mode 100644 index e29664ec5e..0000000000 --- a/app/tests/xAPI/Statement/Validation/StatementValidationAttachmentTest.php +++ /dev/null @@ -1,80 +0,0 @@ -exec($this->getFixturePath() . '/Invalid/Attachment/content-type-is-not-string.json'); - $this->assertEquals('failed', $results['status']); - $this->assertEquals( - \Lang::get('xAPIValidation.errors.type', array( - 'key' => 'contentType', - 'section' => 'attachment', - 'type' => 'Internet Media Type' - )), trim($results['errors'][0]) - ); - } - - public function testLength() - { - $results = $this->exec($this->getFixturePath() . '/Invalid/Attachment/length-is-not-integer.json'); - $this->assertEquals('failed', $results['status']); - $this->assertEquals( - \Lang::get('xAPIValidation.errors.type', array( - 'key' => 'length', - 'section' => 'attachment', - 'type' => 'number' - )), trim($results['errors'][0]) - ); - } - - public function testSha2Required() - { - $results = $this->exec($this->getFixturePath() . '/Invalid/Attachment/missing-sha2.json'); - $this->assertEquals('failed', $results['status']); - $this->assertEquals( - \Lang::get('xAPIValidation.errors.required', array( - 'key' => 'sha2', - 'section' => 'attachment' - )), trim($results['errors'][0]) - ); - } - - public function testDisplayValid() - { - $results = $this->exec($this->getFixturePath() . '/Invalid/Attachment/display.json'); - $this->assertEquals('failed', $results['status']); - $this->assertEquals(\Lang::get('xAPIValidation.errors.langMap', array( - 'key' => 'display', - 'section' => 'attachment' - )), trim($results['errors'][0]) - ); - } - - public function testSha2Valid() - { - $results = $this->exec($this->getFixturePath() . '/Invalid/Attachment/sha2-is-not-valid.json'); - $this->assertEquals('failed', $results['status']); - $this->assertEquals(\Lang::get('xAPIValidation.errors.base64', array( - 'key' => 'sha2', - 'section' => 'attachment' - )), trim($results['errors'][0]) - ); - } - - public function testUsageType() - { - $results = $this->exec($this->getFixturePath() . '/Invalid/Attachment/missing-usage-type.json'); - $this->assertEquals('failed', $results['status']); - $this->assertEquals( - \Lang::get('xAPIValidation.errors.required', array( - 'key' => 'usageType', - 'section' => 'attachment' - )), trim($results['errors'][0]) - ); - } - -} diff --git a/app/tests/xAPI/Statement/Validation/StatementValidationAuthorityTest.php b/app/tests/xAPI/Statement/Validation/StatementValidationAuthorityTest.php deleted file mode 100644 index 0a6a53364c..0000000000 --- a/app/tests/xAPI/Statement/Validation/StatementValidationAuthorityTest.php +++ /dev/null @@ -1,15 +0,0 @@ -exec($this->getFixturePath() . '/Invalid/Authority/Member/wrong-object-type.json'); - $this->assertEquals('failed', $results['status']); - $this->assertEquals(\Lang::get('xAPIValidation.errors.group.groups'), trim($results['errors'][0])); - } - -} diff --git a/app/tests/xAPI/Statement/Validation/StatementValidationObjectTest.php b/app/tests/xAPI/Statement/Validation/StatementValidationObjectTest.php deleted file mode 100644 index 25aa9e01ae..0000000000 --- a/app/tests/xAPI/Statement/Validation/StatementValidationObjectTest.php +++ /dev/null @@ -1,6 +0,0 @@ -exec($path); - $this->assertEquals('passed', $results['status']); - $this->assertEmpty($results['errors']); - - $short_path = substr($path, strpos($path, '/Fixtures/Statements/Valid/') + 27, -5); - $extra_method = str_replace(['//', '/', '-'], ' ', trim($short_path, '/')); - $extra_method = 'extraChecking' . str_replace(' ', '', ucwords($extra_method)); - if (method_exists($this, $extra_method)) { - $this->{$extra_method}($results); - } - } - - protected function extraCheckingActorGroup($results) - { - $this->assertEquals($this->json_input['actor']['member'], $results['statement']['actor']['member']); - } - - public function dataProviderSimple() - { - $data = []; - - foreach (['', 'Actor', 'Object', 'Verb/Display'] as $k) { - foreach (glob($this->getFixturePath() . "/Valid/{$k}/*.json") as $file) { - $data[][] = $file; - } - } - - return $data; - } - -} From 566b3875499cdec2d9aa65d8dcd38e81d1269fe6 Mon Sep 17 00:00:00 2001 From: Ryan Smith <0ryansmith1994@gmail.com> Date: Mon, 20 Apr 2015 10:09:15 +0100 Subject: [PATCH 15/34] Removes testing covered by conformance tests. --- app/tests/StatementContextTest.php | 41 ---------- app/tests/StatementGetTest.php | 61 --------------- app/tests/StatementPostTest.php | 114 --------------------------- app/tests/StatementPutTest.php | 119 ----------------------------- app/tests/StatementTest.php | 27 ------- app/tests/StatementVersionTest.php | 40 ---------- 6 files changed, 402 deletions(-) delete mode 100644 app/tests/StatementContextTest.php delete mode 100644 app/tests/StatementGetTest.php delete mode 100644 app/tests/StatementPostTest.php delete mode 100644 app/tests/StatementPutTest.php delete mode 100644 app/tests/StatementTest.php delete mode 100644 app/tests/StatementVersionTest.php diff --git a/app/tests/StatementContextTest.php b/app/tests/StatementContextTest.php deleted file mode 100644 index 569f257d79..0000000000 --- a/app/tests/StatementContextTest.php +++ /dev/null @@ -1,41 +0,0 @@ - $this->dummyEmail())); - Auth::login($user); - $this->lrs = $this->createLRS(); - $this->statement = App::make('Locker\Repository\Statement\EloquentStatementRepository'); - } - - /** - * The LRS MUST return single Activity Objects as an array of length one - * containing the same Activity. - */ - public function testContextActivities() - { - $stmt = $this->defaultStatment(); - // $parent = new stdClass(); - // $parent->id = 'http://tincanapi.com/GolfExample_TCAPI'; - // $parent->objectType = 'Activity'; - $parent = [ - 'id' => 'http://tincanapi.com/GolfExample_TCAPI', - 'objectType' => 'Activity' - ]; - $contextActivities = ['parent' => [$parent]]; // $parent should be an array not an object. - $stmt['context']['contextActivities'] = $contextActivities; - - $return = $this->createStatement($stmt, $this->lrs); - - $id = $return[0]; - $saved_statement = $this->statement->show($this->lrs->_id, $id)->first(); - // The parent must be array. - $this->assertTrue(is_array($saved_statement['statement']['context']['contextActivities']['parent'])); - } - -} diff --git a/app/tests/StatementGetTest.php b/app/tests/StatementGetTest.php deleted file mode 100644 index 48d3cdfebd..0000000000 --- a/app/tests/StatementGetTest.php +++ /dev/null @@ -1,61 +0,0 @@ - 'quan@ll.com']); - Auth::login($user); - } - - private function _makeRequest($auth, $version) - { - return $this->call("GET", '/data/xAPI/statements', [], [], $this->makeRequestHeaders($auth, $version)); - } - - /** - * Create statements for lrs - * - * @param string $version Make sure LRS response to all valid version. - * @return void - * @dataProvider dataGetAuthService - */ - public function testGetAuthService($version, $expecting_code) - { - $lrs = $this->createLRS(); - - // create client for Auth Service - $auth = [ - 'api_key' => $this->lrs->api['basic_key'], - 'api_secret' => $this->lrs->api['basic_secret'], - ]; - - - // Make sure response data for the get request - $response = $this->_makeRequest($auth, $version); - $this->assertEquals($expecting_code, $response->getStatusCode()); - - $lrs->delete(); - } - - public function dataGetAuthService() { - $data = []; - - foreach (range(0, 20) as $i) { - if (array_rand([true, false])) { - $data[] = ["1.0.{$i}", 200]; - } - } - - //$data[] = ["0.9", 400]; - //$data[] = ["1.1", 400]; - - return $data; - } -} diff --git a/app/tests/StatementPostTest.php b/app/tests/StatementPostTest.php deleted file mode 100644 index 943f13cc1f..0000000000 --- a/app/tests/StatementPostTest.php +++ /dev/null @@ -1,114 +0,0 @@ - 'quan@ll.com']); - Auth::login($user); - } - - private function _makeRequest($param, $method, $auth) - { - return $this->call($method, '/data/xAPI/statements', [], [], $this->makeRequestHeaders($auth), !empty($param) ? json_encode($param) : []); - } - - /** - * Make a post request to LRS - * - * @return void - */ - public function testPostBehavior() - { - $this->createLRS(); - - $vs = $this->defaultStatment(); - $result = $this->createStatement($vs, $this->lrs); - - $statement = App::make('Locker\Repository\Statement\EloquentStatementRepository'); - $createdStatement = $statement->show($this->lrs->_id, $result[0])->first(); - - $param = array( - 'actor' => $createdStatement->statement['actor'], - 'verb' => $createdStatement->statement['verb'], - 'context' => $createdStatement->statement['context'], - 'object' => $createdStatement->statement['object'], - 'id' => $createdStatement->statement['id'], - 'timestamp' => $createdStatement->statement['timestamp'], - ); - - // create client for Auth Service - $auth = [ - 'api_key' => $this->lrs->api['basic_key'], - 'api_secret' => $this->lrs->api['basic_secret'], - ]; - - // case: conflict-matches - /* @var $response Illuminate\Http\JsonResponse */ - $response = $this->_makeRequest($param, "POST", $auth); - $this->assertEquals(200, $response->getStatusCode()); - - // case: conflict nomatch - $param['result'] = new \stdClass(); - try { - $response = $this->_makeRequest($param, "POST", $auth); - } catch (\Exception $ex) { - $this->assertEquals(true, method_exists($ex, 'getStatusCode')); - $this->assertEquals(409, $ex->getStatusCode()); - } - - // Make sure response data for the get request - $responseGet = $this->_makeRequest(new \stdClass(), "GET", $auth); - $this->assertEquals(200, $responseGet->getStatusCode()); - - // Make sure response data for the get request - unset($param['result']); - $responsePost = $this->_makeRequest($param, "POST", $auth); - $this->assertEquals(200, $responsePost->getStatusCode()); - } - - /** - * make a post request to lrs with Auth Service - * - * @return void - */ - public function testPostAuthService() - { - $this->createLRS(); - - $vs = $this->defaultStatment(); - $result = $this->createStatement($vs, $this->lrs); - - $statement = App::make('Locker\Repository\Statement\EloquentStatementRepository'); - $createdStatement = $statement->show($this->lrs->_id, $result[0])->first(); - - $param = [ - 'actor' => $createdStatement->statement['actor'], - 'verb' => $createdStatement->statement['verb'], - 'context' => $createdStatement->statement['context'], - 'object' => $createdStatement->statement['object'], - 'id' => $createdStatement->statement['id'], - 'timestamp' => $createdStatement->statement['timestamp'], - ]; - - // create client for Auth Service - $auth = [ - 'api_key' => $this->lrs->api['basic_key'], - 'api_secret' => $this->lrs->api['basic_secret'], - ]; - - $response = $this->_makeRequest($param, "POST", $auth); - - $responseData = $response->getContent(); - $responseStatus = $response->getStatusCode(); - - $this->assertEquals(200, $responseStatus); - } - -} diff --git a/app/tests/StatementPutTest.php b/app/tests/StatementPutTest.php deleted file mode 100644 index 13d328e418..0000000000 --- a/app/tests/StatementPutTest.php +++ /dev/null @@ -1,119 +0,0 @@ - 'quan@ll.com']); - Auth::login($user); - } - - private function _makeRequest($param, $auth) - { - return $this->call('PUT', '/data/xAPI/statements', ['statementId' => $param['id']], [], $this->makeRequestHeaders($auth), json_encode($param)); - } - - /** - * Create statements for lrs - * - * @return void - */ - public function testPutBehavior() - { - $this->createLRS(); - - $vs = $this->defaultStatment(); - $result = $this->createStatement($vs, $this->lrs); - - $statement = App::make('Locker\Repository\Statement\EloquentStatementRepository'); - $createdStatement = $statement->show($this->lrs->_id, $result[0])->first(); - - $param = array( - 'actor' => $createdStatement->statement['actor'], - 'verb' => $createdStatement->statement['verb'], - 'context' => $createdStatement->statement['context'], - 'object' => $createdStatement->statement['object'], - 'id' => $createdStatement->statement['id'], - 'timestamp' => $createdStatement->statement['timestamp'], - ); - - // create client for Auth Service - $auth = [ - 'api_key' => $this->lrs->api['basic_key'], - 'api_secret' => $this->lrs->api['basic_secret'], - ]; - - // case: conflict-matches - $response = $this->_makeRequest($param, $auth); - $responseData = method_exists($response, 'getData'); - $responseStatus = $response->getStatusCode(); - - $this->assertEquals(204, $responseStatus); - $this->assertEquals(false, $responseData); - - // case: conflict nomatch - $param['result'] = new \stdClass(); - try { - $response = $this->_makeRequest($param, $auth); - } catch (\Exception $ex) { - $this->assertEquals(true, method_exists($ex, 'getStatusCode')); - $this->assertEquals(409, $ex->getStatusCode()); - } - } - - /** - * Create statements for lrs with Auth Service - * - * @return void - */ - public function testPutAuthService() - { - $this->createLRS(); - - $vs = $this->defaultStatment(); - $result = $this->createStatement($vs, $this->lrs); - - $statement = App::make('Locker\Repository\Statement\EloquentStatementRepository'); - $createdStatement = $statement->show($this->lrs->_id, $result[0])->first(); - - $param = [ - 'actor' => $createdStatement->statement['actor'], - 'verb' => $createdStatement->statement['verb'], - 'context' => $createdStatement->statement['context'], - 'object' => $createdStatement->statement['object'], - 'id' => $createdStatement->statement['id'], - 'timestamp' => $createdStatement->statement['timestamp'], - ]; - - // create client for Auth Service - // create client for Auth Service - $auth = [ - 'api_key' => $this->lrs->api['basic_key'], - 'api_secret' => $this->lrs->api['basic_secret'], - ]; - - $response = $this->_makeRequest($param, $auth); - $responseData = method_exists($response, 'getData'); - $responseStatus = $response->getStatusCode(); - - $this->assertEquals(204, $responseStatus); - $this->assertEquals(false, $responseData); - } - - public function tearDown() - { - parent::tearDown(); - - // Need LRS table is empty because waiting the getLrsBySubdomain() - if ($this->lrs) { - $this->lrs->delete(); - } - } - -} diff --git a/app/tests/StatementTest.php b/app/tests/StatementTest.php deleted file mode 100644 index 6abdfbc996..0000000000 --- a/app/tests/StatementTest.php +++ /dev/null @@ -1,27 +0,0 @@ - 'quan@ll.com')); - Auth::login($user); - $this->createLRS(); - } - - /** - * Create statements for lrs - * - * @return void - */ - public function testCreate() { - $vs = $this->defaultStatment(); - - $statement = App::make('Locker\Repository\Statement\EloquentStatementRepository'); - $result = $statement->create([json_decode(json_encode($vs))], $this->lrs); - - $this->assertTrue(is_array($result)); - } - -} diff --git a/app/tests/StatementVersionTest.php b/app/tests/StatementVersionTest.php deleted file mode 100644 index 7bb7cd62d7..0000000000 --- a/app/tests/StatementVersionTest.php +++ /dev/null @@ -1,40 +0,0 @@ - 'andy@ll.com']); - Auth::login($user); - - $this->lrs = $this->createLRS(); - } - - /** - * If no 'version' provided in statement, LRS must save '1.0.0' as default - * as value. - */ - public function testMissingVersion() - { - $statement = $this->defaultStatment(); - - // make sure there is no version - $this->assertTrue(!isset($statement['version']), 'There is no version provided'); - - $return = $this->createStatement($statement, $this->lrs); - - $id = reset($return); - - /* @var $saved_statement Statement */ - $saved_statement = App::make('Locker\Repository\Statement\EloquentStatementRepository')->show($this->lrs->_id, $id)->first(); - $this->assertTrue(isset($saved_statement->statement['version']), 'There is no version provided'); - $this->assertEquals('1.0.0', $saved_statement->statement['version']); - } - -} From e93193a51587cada5e9bbca67d2966be168699bb Mon Sep 17 00:00:00 2001 From: Ryan Smith <0ryansmith1994@gmail.com> Date: Mon, 20 Apr 2015 10:09:58 +0100 Subject: [PATCH 16/34] Completes statement repo split. --- .../repository/Statement/EloquentIndexer.php | 2 +- .../repository/Statement/EloquentInserter.php | 31 ++++---- .../repository/Statement/EloquentLinker.php | 73 ++++++++++--------- .../repository/Statement/EloquentReader.php | 11 +++ .../Statement/EloquentRepository.php | 17 ++++- .../repository/Statement/EloquentShower.php | 11 +-- .../repository/Statement/EloquentStorer.php | 2 +- .../repository/Statement/EloquentVoider.php | 6 +- .../repository/Statement/FileAttacher.php | 62 +++++++++++++++- 9 files changed, 144 insertions(+), 71 deletions(-) diff --git a/app/locker/repository/Statement/EloquentIndexer.php b/app/locker/repository/Statement/EloquentIndexer.php index a0d3a8654b..020236fd63 100644 --- a/app/locker/repository/Statement/EloquentIndexer.php +++ b/app/locker/repository/Statement/EloquentIndexer.php @@ -134,7 +134,7 @@ public function format(Builder $builder, IndexOptions $opts) { ->take($opts->getOpt('limit')) ->get() ->map(function (Model $model) use ($opts, $formatter) { - return $formatter($model->statement, $opts); + return $formatter($this->formatModel($model), $opts); }); } diff --git a/app/locker/repository/Statement/EloquentInserter.php b/app/locker/repository/Statement/EloquentInserter.php index d7f10c2c65..3edc30177d 100644 --- a/app/locker/repository/Statement/EloquentInserter.php +++ b/app/locker/repository/Statement/EloquentInserter.php @@ -35,8 +35,7 @@ private function checkForConflict(\stdClass $statement, StoreOptions $opts) { ->first(); if ($duplicate === null) return; - $duplicate =json_decode(json_encode($duplicate->statement)); - $this->compareForConflict($statement, $duplicate); + $this->compareForConflict($statement, $this->formatModel($duplicate)); } /** @@ -47,13 +46,11 @@ private function checkForConflict(\stdClass $statement, StoreOptions $opts) { * @throws Exceptions\Conflict */ private function compareForConflict(\stdClass $statement_x, \stdClass $statement_y) { - $encoded_x = json_encode($statement_x); - $encoded_y = json_encode($statement_y); - $decoded_x = $this->decodeStatementMatch($encoded_x); - $decoded_y = $this->decodeStatementMatch($encoded_y); - if ($decoded_x !== $decoded_y) { + $matchable_x = $this->matchableStatement($statement_x); + $matchable_y = $this->matchableStatement($statement_y); + if ($matchable_x != $matchable_y) { throw new Exceptions\Conflict( - "Conflicts\r\n`$encoded_x`\r\n`$encoded_y`." + "Conflicts\r\n`{json_encode($statement_x)}`\r\n`{json_encode($statement_y)}`." ); }; } @@ -61,16 +58,14 @@ private function compareForConflict(\stdClass $statement_x, \stdClass $statement /** * Decodes the encoded statement. * Removes properties not necessary for matching. - * @param String $encoded_statement - * @return [String => Mixed] $decoded_statement + * @param \stdClass $statement + * @return \stdClass $statement */ - private function decodeStatementMatch($encoded_statement) { - $decoded_statement = json_decode($encoded_x, true); - array_multisort($decoded_statement); - ksort($decoded_statement); - unset($decoded_statement['stored']); - unset($decoded_statement['authority']); - return $decoded_statement; + private function matchableStatement(\stdClass $statement) { + $statement = json_decode(json_encode($statement)); + unset($statement->stored); + unset($statement->authority); + return $statement; } /** @@ -85,7 +80,7 @@ private function constructModel(\stdClass $statement, StoreOptions $opts) { 'statement' => $statement, 'active' => false, 'voided' => false, - 'timestamp' => new MongoDate(strtotime($statement->timestamp)) + 'timestamp' => new \MongoDate(strtotime($statement->timestamp)) ]; } diff --git a/app/locker/repository/Statement/EloquentLinker.php b/app/locker/repository/Statement/EloquentLinker.php index 6688abc635..b53e06e7d8 100644 --- a/app/locker/repository/Statement/EloquentLinker.php +++ b/app/locker/repository/Statement/EloquentLinker.php @@ -1,6 +1,7 @@ downed = new Collection(); $this->to_update = array_map(function (\stdClass $statement) use ($opts) { - return $this->getModel($statement, $opts); + return $this->getModel($statement->id, $opts); }, $statements); while (count($this->to_update) > 0) { @@ -31,24 +33,24 @@ public function updateReferences(array $statements, StoreOptions $opts) { * @param \stdClass $statement * @return Boolean */ - private function isReferencing(Model $statement) { + private function isReferencing(\stdClass $statement) { return ( - isset($model->statement->object->objectType) && - $model->statement->object->objectType === 'StatementRef' + isset($statement->object->objectType) && + $statement->object->objectType === 'StatementRef' ); } /** * Gets the statement as an associative array from the database. - * @param \stdClass $statement + * @param String $statement_id Statement's UUID. * @param StoreOptions $opts * @return [Model] */ - private function getModel(\stdClass $statement, StoreOptions $opts) { - $statement_id = $statement->id; + private function getModel($statement_id, StoreOptions $opts) { $model = $this->where($opts) ->where('statement.id', $statement_id) ->first(); + return $model; } @@ -60,13 +62,15 @@ private function getModel(\stdClass $statement, StoreOptions $opts) { * @return [Model] */ private function upLink(Model $model, array $visited, StoreOptions $opts) { - if (in_array($model->statement->id, $visited)) return []; - $visited[] = $model->statement->id; - $up_refs = $this->upRefs($model, $opts); + $statement = $this->formatModel($model); + if (in_array($statement->id, $visited)) return []; + $visited[] = $statement->id; + $up_refs = $this->upRefs($statement, $opts); if ($up_refs->count() > 0) { - return $up_refs->map(function ($up_ref) use ($opts, $visited) { - if (in_array($up_ref, $this->downed)) return; - $this->downed = array_merge($this->downed, $this->upLink($up_ref, $visited, $opts)); + return $up_refs->each(function ($up_ref) use ($opts, $visited) { + if ($this->downed->has($up_ref->_id)) return; + $this->downed->merge($this->upLink($up_ref, $visited, $opts)); + return $up_ref; })->values(); } else { return $this->downLink($model, [], $opts); @@ -81,14 +85,15 @@ private function upLink(Model $model, array $visited, StoreOptions $opts) { * @return [Model] */ private function downLink(Model $model, array $visited, StoreOptions $opts) { + $statement = $this->formatModel($model); if (in_array($model, $visited)) { return array_slice($visited, array_search($model, $visited)); } $visited[] = $model; - $down_ref = $this->downRef($model, $opts); + $down_ref = $this->downRef($statement, $opts); if ($down_ref !== null) { $refs = $this->downLink($down_ref, $visited, $opts); - $this->setRefs($model, $refs, $opts); + $this->setRefs($statement, $refs, $opts); $this->unQueue($model); return array_merge([$model], $refs); } else { @@ -99,41 +104,43 @@ private function downLink(Model $model, array $visited, StoreOptions $opts) { /** * Gets the statements referencing the given statement. - * @param Model $model + * @param \stdClass $statement * @param StoreOptions $opts - * @return [Model] + * @return [\stdClass] */ - private function upRefs(Model $model, StoreOptions $opts) { + private function upRefs(\stdClass $statement, StoreOptions $opts) { return $this->where($opts) - ->where('statement.object.id', $model->statement->id) + ->where('statement.object.id', $statement->id) ->where('statement.object.objectType', 'StatementRef') ->get(); } /** * Gets the statement referred to by the given statement. - * @param Model $model + * @param \stdClass $statement * @param StoreOptions $opts * @return Model */ - private function downRef(Model $model, StoreOptions $opts) { - if (!$this->isReferencing($model)) return null; - return $this->where($opts) - ->where('statement.id', $model->statement->object->id) - ->first(); + private function downRef(\stdClass $statement, StoreOptions $opts) { + if (!$this->isReferencing($statement)) return null; + return $this->getModel($statement->object->id, $opts); } /** * Updates the refs for the given statement. - * @param Model $model - * @param [[String => mixed]] $refs Statements that are referenced by the given statement. + * @param \stdClass $statement + * @param [\stdClass] $refs Statements that are referenced by the given statement. * @param StoreOptions $opts */ - private function setRefs(Model $model, array $refs) { - $model->refs = array_map(function ($ref) { - return $ref->statement; - }, $refs); - $model->save(); + private function setRefs(\stdClass $statement, array $refs, StoreOptions $opts) { + $this->where($opts) + ->where('statement.id', $statement->id) + ->update([ + 'refs' => array_map(function ($ref) { + if (get_class($ref) === 'stdClass') \Log::info(json_encode($ref)); + return $ref->statement; + }, $refs) + ]); } /** diff --git a/app/locker/repository/Statement/EloquentReader.php b/app/locker/repository/Statement/EloquentReader.php index 75cc51a809..48b17cf7ad 100644 --- a/app/locker/repository/Statement/EloquentReader.php +++ b/app/locker/repository/Statement/EloquentReader.php @@ -1,5 +1,7 @@ model)->where('lrs._id', $opts->getOpt('lrs_id')); } + + /** + * Gets the statement from the model as an Object. + * @param Model $model + * @return \stdClass + */ + protected function formatModel(Model $model) { + return json_decode(json_encode($model->statement)); + } } diff --git a/app/locker/repository/Statement/EloquentRepository.php b/app/locker/repository/Statement/EloquentRepository.php index a2fb5d5a6f..effbeac225 100644 --- a/app/locker/repository/Statement/EloquentRepository.php +++ b/app/locker/repository/Statement/EloquentRepository.php @@ -4,6 +4,7 @@ interface Repository { public function store(array $statements, array $attachments, array $opts); public function index(array $opts); public function show($id, array $opts); + public function getAttachments(array $statements, array $opts); } class EloquentRepository implements Repository { @@ -15,6 +16,7 @@ public function __construct() { $this->storer = new EloquentStorer(); $this->indexer = new EloquentIndexer(); $this->shower = new EloquentShower(); + $this->attacher = new FileAttacher(); } /** @@ -31,14 +33,15 @@ public function store(array $statements, array $attachments, array $opts) { /** * Gets all of the available models with the options. * @param [String => Mixed] $opts - * @return [[\stdClass], Int] Array containing the statements and count. + * @return [[\stdClass], Int, [String => Mixed]] Array containing the statements, count, and opts. */ public function index(array $opts) { $opts = new IndexOptions($opts); $builder = $this->indexer->index($opts); return [ $this->indexer->format($builder, $opts), - $this->indexer->count($builder, $opts) + $this->indexer->count($builder, $opts), + $opts->options ]; } @@ -51,4 +54,14 @@ public function index(array $opts) { public function show($id, array $opts) { return $this->shower->show($id, new ShowOptions($opts)); } + + /** + * Gets the attachments for the given statements and options. + * @param [\stdClass] $statements + * @param [String => Mixed] $opts + * @return [\stdClass] + */ + public function getAttachments(array $statements, array $opts) { + return $this->attacher->index($statements, new IndexOptions($opts)); + } } \ No newline at end of file diff --git a/app/locker/repository/Statement/EloquentShower.php b/app/locker/repository/Statement/EloquentShower.php index b6c57fee60..4086044e5e 100644 --- a/app/locker/repository/Statement/EloquentShower.php +++ b/app/locker/repository/Statement/EloquentShower.php @@ -25,15 +25,6 @@ public function show($id, ShowOptions $opts) { if ($model === null) throw new Exceptions\NotFound($id, $this->model); - return $this->format($model); - } - - /** - * Formats the model before returning. - * @param Model $model - * @return Model - */ - public function format(Model $model) { - return json_decode(json_encode($model->statement)); + return $this->formatModel($model); } } diff --git a/app/locker/repository/Statement/EloquentStorer.php b/app/locker/repository/Statement/EloquentStorer.php index 54ed2a7838..82569e82c6 100644 --- a/app/locker/repository/Statement/EloquentStorer.php +++ b/app/locker/repository/Statement/EloquentStorer.php @@ -64,7 +64,7 @@ private function constructValidStatements(array $statements, StoreOptions $opts) // Validates statement. $constructed_statement = new XAPIStatement($statement); Helpers::validateAtom($constructed_statement, 'statement'); - $statement = $constructed_statement->toValue(); + $statement = $constructed_statement->getValue(); // Adds $statement to $constructed. if (isset($constructed[$statement->id])) { diff --git a/app/locker/repository/Statement/EloquentVoider.php b/app/locker/repository/Statement/EloquentVoider.php index de89dac5cb..e3ce8b3b09 100644 --- a/app/locker/repository/Statement/EloquentVoider.php +++ b/app/locker/repository/Statement/EloquentVoider.php @@ -27,12 +27,10 @@ public function voidStatements(array $statements, StoreOptions $opts) { private function voidStatement(\stdClass $voider, StoreOptions $opts) { if (!$this->isVoiding($voider)) return; - $voided = $this->where($opts) - ->where('statement.id', $voider->object->id) - ->first(); + $voided = $this->getModel($voider->object->id, $opts); if ($voided !== null) { - if ($this->isVoidinging($voided->statement)) throw new \Exception(trans( + if ($this->isVoidinging($this->formatModel($voided))) throw new \Exception(trans( 'xapi.errors.void_voider' )); diff --git a/app/locker/repository/Statement/FileAttacher.php b/app/locker/repository/Statement/FileAttacher.php index d4e77f62d7..a94d7a34c7 100644 --- a/app/locker/repository/Statement/FileAttacher.php +++ b/app/locker/repository/Statement/FileAttacher.php @@ -1,13 +1,71 @@ getOpt('lrs_id').'/attachments/'; + $dir = $this->getDir($opts); if (!is_dir($dir)) mkdir($dir, null, true); foreach ($attachments as $attachment) { - file_put_contents($dir.$attachment->file, $attachment->content); + $ext = $this->getExt($attachment->content_type); + if ($ext === false) throw new Exceptions\Exception( + 'This file type cannot be supported' + ); + + $file = $attachment->hash.$ext; + file_put_contents($dir.'.'.$file, $attachment->content); + } + } + + /** + * Gets all of the attachments for the given statements. + * @param [\stdClass] $statements + * @param IndexOptions $opts + * @return [\stdClass] + */ + public function index(array $statements, IndexOptions $opts) { + $dir = $this->getDir($opts); + + $attachments = []; + foreach ($statements as $statement) { + $attachments = array_merge($attachments, array_map(function ($attachment) use ($dir) { + $ext = $this->getExt($attachment->contentType); + $filename = $attachment->sha2.'.'.$ext; + return (object) [ + 'content_type' => $attachment['contentType'], + 'hash' => $attachment->sha2, + 'content' => file_get_contents($dir.$filename) + ]; + }, isset($statement->attachments) ? $statement->attachments : [])); } + + return $attachments; + } + + /** + * Gets the extension from the given content type. + * @param String $content_type + * @return String + */ + private function getExt($content_type) { + return array_search($content_type, FileTypes::getMap()); + } + + /** + * Gets the directory for attachments with the given options. + * @param Options $opts + * @return String + */ + private function getDir(Options $opts) { + return Helpers::getEnvVar('LOCAL_FILESTORE').'/'.$opts->getOpt('lrs_id').'/attachments/'; } } From 3377c9048228ec15fb9ae69390cf470d80ad0558 Mon Sep 17 00:00:00 2001 From: Ryan Smith <0ryansmith1994@gmail.com> Date: Mon, 20 Apr 2015 10:10:54 +0100 Subject: [PATCH 17/34] Integrates split statement repo. --- app/commands/StatementMigrateCommand.php | 2 +- app/controllers/LrsController.php | 2 +- app/controllers/SiteController.php | 2 +- app/controllers/StatementController.php | 2 +- app/controllers/xapi/ActivityController.php | 4 +- app/controllers/xapi/StatementController.php | 275 ++---------------- app/locker/helpers/Attachments.php | 153 +++++----- app/locker/helpers/Helpers.php | 15 + .../Activity/ActivityRepository.php | 9 - .../Activity/EloquentActivityRepository.php | 49 ---- .../repository/RepositoryServiceProvider.php | 8 +- 11 files changed, 120 insertions(+), 401 deletions(-) delete mode 100644 app/locker/repository/Activity/ActivityRepository.php delete mode 100644 app/locker/repository/Activity/EloquentActivityRepository.php diff --git a/app/commands/StatementMigrateCommand.php b/app/commands/StatementMigrateCommand.php index fc421d49ec..2af1bab5fb 100644 --- a/app/commands/StatementMigrateCommand.php +++ b/app/commands/StatementMigrateCommand.php @@ -55,7 +55,7 @@ public function fire() { }); // Uses the repository to migrate the statements. - $repo = App::make('Locker\Repository\Statement\EloquentStatementRepository'); + $repo = App::make('Locker\Repository\Statement\EloquentVoider'); $repo->updateReferences($statements_array, $lrs); $repo->voidStatements($statements_array, $lrs); diff --git a/app/controllers/LrsController.php b/app/controllers/LrsController.php index 3f7bb07034..9bc9f3e8f6 100644 --- a/app/controllers/LrsController.php +++ b/app/controllers/LrsController.php @@ -1,7 +1,7 @@ activity = $activity; } /** diff --git a/app/controllers/xapi/StatementController.php b/app/controllers/xapi/StatementController.php index fcfd1ecce2..4dda17caf6 100644 --- a/app/controllers/xapi/StatementController.php +++ b/app/controllers/xapi/StatementController.php @@ -1,7 +1,6 @@ statement = $statement; - $this->query = $query; + $this->index_controller = new StatementIndexController($statement); + $this->store_controller = new StatementStoreController($statement); } /** @@ -53,286 +53,49 @@ public function get() { } /** - * Deals with multipart requests. - * @return ['content' => $content, 'attachments' => $attachments]. - */ - private function getParts() { - $content = \LockerRequest::getContent(); - $contentType = \LockerRequest::header('content-type'); - $types = explode(';', $contentType, 2); - $mimeType = count($types) >= 1 ? $types[0] : $types; - - if ($mimeType == 'multipart/mixed') { - $components = Attachments::setAttachments($contentType, $content); - - // Returns 'formatting' error. - if (empty($components)) { - throw new Exceptions\Exception('There is a problem with the formatting of your submitted content.'); - } - - // Returns 'no attachment' error. - if (!isset($components['attachments'])) { - throw new Exceptions\Exception('There were no attachments.'); - } - - $content = $components['body']; - $attachments = $components['attachments']; - } else { - $attachments = ''; - } - - return [ - 'content' => $content, - 'attachments' => $attachments - ]; - } - - private function checkContentType() { - $contentType = \LockerRequest::header('Content-Type'); - if ($contentType === null) { - throw new Exceptions\Exception('Missing Content-Type.'); - } - - $validator = new \app\locker\statements\xAPIValidation(); - $validator->checkTypes('Content-Type', $contentType, 'contentType', 'headers'); - if ($validator->getStatus() !== 'passed') { - throw new Exceptions\Exception(implode(',', $validator->getErrors())); - } - } - - /** - * Stores (POSTs) a newly created statement in storage. + * Updates (PUTs) Statement with the given id. * @return Response */ - public function store() { - // Validates request. + public function update() { + // Runs filters. if ($result = $this->checkVersion()) return $result; - if ($result = $this->checkContentType()) return $result; - if (\LockerRequest::hasParam(self::STATEMENT_ID)) { - throw new Exceptions\Exception('Statement ID parameter is invalid.'); - } - - $parts = $this->getParts(); - $content = $parts['content']; - $attachments = $parts['attachments']; - - $statements = json_decode($content); - - if ($statements === null && $content != 'null' && $content != '') { - throw new Exceptions\Exception('Invalid JSON'); - } - - // Ensures that $statements is an array. - if (!is_array($statements)) { - $statements = [$statements]; - } - - // Saves $statements with $attachments. - return $this->statement->create( - $statements, - $this->lrs, - $attachments - ); + $this->store_controller->update($this->lrs->_id); } /** * Updates (PUTs) Statement with the given id. * @return Response */ - public function update() { + public function store() { // Runs filters. if ($result = $this->checkVersion()) return $result; - if ($result = $this->checkContentType()) return $result; - - $parts = $this->getParts(); - $content = $parts['content']; - $attachments = $parts['attachments']; - - // Decodes the statement. - $statement = json_decode($content); - - if ($statement === null && $content != 'null' && $content != '') { - throw new Exceptions\Exception('Invalid JSON'); - } - - $statementId = \LockerRequest::getParam(self::STATEMENT_ID); - - // Returns a error if identifier is not present. - if (!$statementId) { - throw new Exceptions\Exception('A statement ID is required to PUT.'); - } - - // Attempts to create the statement if `statementId` is present. - $statement->id = $statementId; - $this->statement->create([$statement], $this->lrs, $attachments); - return \Response::make('', 204); + $this->store_controller->store($this->lrs->_id); } /** * Gets an array of statements. - * https://github.com/adlnet/xAPI-Spec/blob/master/xAPI.md#723-getstatements - * @return StatementResult + * @return Response */ public function index() { - // Gets the filters from the request. - $filters = [ - 'agent' => $this->validatedParam('agent', 'agent'), - 'activity' => $this->validatedParam('irl', 'activity'), - 'verb' => $this->validatedParam('irl', 'verb'), - 'registration' => $this->validatedParam('uuid', 'registration'), - 'since' => $this->validatedParam('isoTimestamp', 'since'), - 'until' => $this->validatedParam('isoTimestamp', 'until'), - 'active' => $this->validatedParam('boolean', 'active', true), - 'voided' => $this->validatedParam('boolean', 'voided', false) - ]; - - - // Gets the options/flags from the request. - $options = [ - 'related_activities' => $this->validatedParam('boolean', 'related_activities', false), - 'related_agents' => $this->validatedParam('boolean', 'related_agents', false), - 'ascending' => $this->validatedParam('boolean', 'ascending', false), - 'format' => $this->validatedParam('string', 'format', 'exact'), - 'offset' => $this->validatedParam('int', 'offset', 0), - 'limit' => $this->validatedParam('int', 'limit', 100), - 'attachments' => $this->validatedParam('boolean', 'attachments', false) - ]; - - // Gets the $statements from the LRS (with the $lrsId) that match the $filters with the $options. - $statements = $this->statement->index( - $this->lrs->_id, - $filters, - $options - ); - - $total = $statements->count(); - - // Gets the statements and uses offset and limit options. - $statements->skip((int) $options['offset']); - $statements->take((int) $options['limit']); - $statements = $statements->get()->toArray(); - - // Selects an output format. - if ($options['format'] === 'ids') { - $statements = $this->statement->toIds($statements); - } else if ($options['format'] === 'canonical') { - $langs = \Request::header('Accept-Language'); - $langs = $langs !== '' ? explode(',', $langs) : []; - $statements = $this->statement->toCanonical($statements, $langs); - } - - // Returns the StatementResult object. - $statement_result = json_encode($this->makeStatementObject($statements, [ - 'total' => $total, - 'offset' => $options['offset'], - 'limit' => $options['limit'] - ])); - - if ($options['attachments'] === true) { - $boundary = 'abcABC0123\'()+_,-./:=?'; - $content_type = 'multipart/mixed; boundary='.$boundary; - $statement_result = "Content-Type:application/json\r\n\r\n".$statement_result; - $body = "--$boundary\r\n".implode( - "\r\n--$boundary\r\n", - array_merge([$statement_result], $this->statement->getAttachments($statements, $this->lrs->_id)) - )."\r\n--$boundary--"; - } else { - $content_type = 'application/json;'; - $body = $statement_result; - } - - // Creates the response. - return \Response::make($body, BaseController::OK, [ - 'Content-Type' => $content_type, - 'X-Experience-API-Consistent-Through' => $this->statement->getCurrentDate() - ]);; + return $this->index_controller->index($this->lrs->_id); } /** * Gets the statement with the given $id. - * @param UUID $id + * @param String $id Statement's UUID. * @param boolean $voided determines if the statement is voided. - * @return Statement + * @return Response */ public function show($id, $voided = false) { // Runs filters. if ($result = $this->checkVersion()) return $result; - $statement = $this->statement->show($this->lrs->_id, $id, $voided)->first(); - if ($statement) { - $dotted_statement = \Locker\Helpers\Helpers::replaceHtmlEntity( - $statement->statement - ); - return \Response::json($dotted_statement, 200); - } else { - throw new Exceptions\NotFound($id, 'Statement'); - } - } - - /** - * Constructs a response for $statements. - * @param array $statements Statements to return. - * @param array $params Filter. - * @param array $debug Log for debgging information. - * @return response - **/ - private function makeStatementObject(array $statements, array $options) { - // Merges options with default options. - $options = array_merge([ - 'total' => count($statements), - 'offset' => null, - 'limit' => null - ], $options); - - // Replaces '&46;' in keys with '.' in statements. - // http://docs.learninglocker.net/docs/statements#quirks - $statements = $statements ?: []; - $statements = \Locker\Helpers\Helpers::replaceHtmlEntity($statements); - foreach ($statements as &$s) { - $s = $s->statement; - } + $statement = $this->statements->show($id, [ + 'lrs_id' => $this->lrs->_id, + 'voided' => $voided + ]); - // Creates the statement result. - $statement_result = [ - 'more' => $this->getMoreLink($options['total'], $options['limit'], $options['offset']), - 'statements' => $statements - ]; - - return $statement_result; - } - - /** - * Constructs the "more link" for a statement response. - * @param Integer $total Number of statements that can be returned for the given request parameters. - * @param Integer $limit Number of statements to be outputted in the response. - * @param Integer $offset Number of statements being skipped. - * @return String A URL that can be used to get more statements for the given request parameters. - */ - private function getMoreLink($total, $limit, $offset) { - // Uses defaults. - $total = $total ?: 0; - $limit = $limit ?: 100; - $offset = $offset ?: 0; - - // Calculates the $next_offset. - $next_offset = $offset + $limit; - if ($total <= $next_offset) return ''; - - // Changes (when defined) or appends (when undefined) offset. - $query = \Request::getQueryString(); - $statement_route = \URL::route('xapi.statement', [], false); - $current_url = $query ? $statement_route.'?'.$query : $statement_route; - - if (strpos($query, "offset=$offset") !== false) { - return str_replace( - 'offset=' . $offset, - 'offset=' . $next_offset, - $current_url - ); - } else { - $separator = strpos($current_url, '?') !== False ? '&' : '?'; - return $current_url . $separator . 'offset=' . $next_offset; - } + return \Response::json(Helpers::replaceHtmlEntity($statement), 200); } /** diff --git a/app/locker/helpers/Attachments.php b/app/locker/helpers/Attachments.php index bfb331b633..27c11dfdd4 100644 --- a/app/locker/helpers/Attachments.php +++ b/app/locker/helpers/Attachments.php @@ -1,5 +1,6 @@ getBoundary($content_type); // Fetch each part of the multipart document - $parts = array_slice(explode($boundary, $incoming_statement), 1); - $data = array(); + $parts = array_slice(explode($boundary, $content), 1); $raw_headers = $body = ''; - //loop through all parts on the body foreach ($parts as $count => $part) { - // At the end of the file, break + // Stops at the end of the file. if ($part == "--") break; // Determines the delimiter. - $delim = "\n"; - if (strpos($part, "\r".$delim) !== false) $delim = "\r".$delim; + $delim = strpos($part, "\r\n") ? "\r\n" : "\n"; // Separate body contents from headers $part = ltrim($part, $delim); list($raw_headers, $body) = explode($delim.$delim, $part, 2); + $headers = $this->getHeaders($raw_headers, $delim); - // Parse headers and separate so we can access - $raw_headers = explode($delim, $raw_headers); - $headers = array(); - foreach ($raw_headers as $header) { - list($name, $value) = explode(':', $header); - $headers[strtolower($name)] = ltrim($value, ' '); - } - - //the first part must be statements - if( $count == 0 ){ - //this is part one, which must be statements - if( $headers['content-type'] !== 'application/json' ){ - \App::abort(400, 'Statements must make up the first part of the body.'); + if ($count == 0) { + if ($headers['content-type'] !== 'application/json') { + throw new Exceptions\Exception('Statements must make up the first part of the body.'); } - //get sha2 hash from each statement - $set_body = json_decode($body, true); - if( is_array(json_decode($body)) ){ - foreach($set_body as $a){ - foreach($a['attachments'] as $attach){ - $sha_hashes[] = $attach['sha2']; - } - } - }else{ - foreach($set_body['attachments'] as $attach){ - $sha_hashes[] = $attach['sha2']; + // Gets hash from each statement. + $statements = json_decode($body); + $statements = is_array($statements) ? $statements : [$statements]; + foreach ($statements as $statement){ + foreach($statement->attachments as $attachment){ + $sha_hashes[] = $attachment->sha2; } } - //set body which will = statements $return['body'] = $body; + } else { + $this->validateHeaders($headers); + $return['attachments'][$count] = (object) [ + 'hash' => $headers['x-experience-api-hash'], + 'content_type' => $headers['content-type'], + 'content' => $body + ]; + } + } - }else{ - - //get the attachment type (Should this be required? @todo) - if( !isset($headers['content-type']) ){ - \App::abort(400, 'You need to set a content type for your attachments.'); - } - - //get the correct ext if valid - $fileTypes = new \Locker\Repository\Document\FileTypes; - $ext = array_search( $headers['content-type'], $fileTypes::getMap() ); - if( $ext === false ){ - \App::abort(400, 'This file type cannot be supported'); - } - - //if content-transfer-encoding is not binary, reject attachment @todo - // if( !isset($headers['content-transfer-encoding']) || $headers['content-transfer-encoding'] !== 'binary' ){ - // \App::abort(400, 'This is the wrong encoding type'); - // } - - //check X-Experience-API-Hash is set, otherwise reject @todo - if( !isset($headers['x-experience-api-hash']) || $headers['x-experience-api-hash'] == ''){ - \App::abort(400, 'Attachments require an api hash.'); - } + return $return; + } - //check x-experience-api-hash is contained within a statement - if( !in_array($headers['x-experience-api-hash'], $sha_hashes)){ - \App::abort(400, 'Attachments need to contain x-experience-api-hash that is declared in statement.'); - } + /** + * Gets the boundary from the content type. + * @param String $raw_headers + * @param String $delim + * @return [String => Mixed] + */ + private function getHeaders($raw_headers, $delim) { + $raw_headers = explode($delim, $raw_headers); + $headers = []; + foreach ($raw_headers as $header) { + list($name, $value) = explode(':', $header); + $headers[strtolower($name)] = ltrim($value, ' '); + } + return $headers; + } - $return['attachments'][$count] = $part; + /** + * Gets the boundary from the content type. + * @param String $content_type + * @return String + */ + private function getBoundary($content_type) { + preg_match('/boundary=(.*)$/', $content_type, $matches); + if (!isset($matches[1])) throw new Exceptions\Exception( + 'You need to set a boundary if submitting attachments.' + ); + return '--'.$matches[1]; + } - } + /** + * Validates the attachment headers. + * @param [String => Mixed] $headers + */ + private function validateHeaders(array $headers) { + if (!isset($headers['content-type'])) { + throw new Exceptions\Exception('You need to set a content type for your attachments.'); + } + //get the correct ext if valid + $ext = array_search($headers['content-type'], FileTypes::getMap()); + if ($ext === false) { + throw new Exceptions\Exception('This file type cannot be supported'); } - return $return; + //check X-Experience-API-Hash is set, otherwise reject @todo + if (!isset($headers['x-experience-api-hash']) || $headers['x-experience-api-hash'] == '') { + throw new Exceptions\Exception('Attachments require an api hash.'); + } + //check x-experience-api-hash is contained within a statement + if (!in_array($headers['x-experience-api-hash'], $sha_hashes)) { + throw new Exceptions\Exception( + 'Attachments need to contain x-experience-api-hash that is declared in statement.' + ); + } } } diff --git a/app/locker/helpers/Helpers.php b/app/locker/helpers/Helpers.php index e828315a22..f52f5485b9 100644 --- a/app/locker/helpers/Helpers.php +++ b/app/locker/helpers/Helpers.php @@ -184,4 +184,19 @@ static function getCurrentDate() { $current_date->setTimezone(new \DateTimeZone(\Config::get('app.timezone'))); return $current_date->format('Y-m-d\TH:i:s.uP'); } + + /** + * Gets the CORS headers. + * @return [String => Mixed] CORS headers. + */ + static function getCORSHeaders() { + return [ + 'Access-Control-Allow-Origin' => \Request::root(), + 'Access-Control-Allow-Methods' => 'GET, PUT, POST, DELETE, OPTIONS', + 'Access-Control-Allow-Headers' => 'Origin, Content-Type, Accept, Authorization, X-Requested-With, X-Experience-API-Version, X-Experience-API-Consistent-Through, Updated', + 'Access-Control-Allow-Credentials' => 'true', + 'X-Experience-API-Consistent-Through' => Helpers::getCurrentDate(), + 'X-Experience-API-Version' => '1.0.1' + ]; + } } diff --git a/app/locker/repository/Activity/ActivityRepository.php b/app/locker/repository/Activity/ActivityRepository.php deleted file mode 100644 index cf276d5165..0000000000 --- a/app/locker/repository/Activity/ActivityRepository.php +++ /dev/null @@ -1,9 +0,0 @@ -activity = $activity; - - } - - /** - * This is a temp solution, we need something better depending - * on authority to update activity stored. - * - **/ - public function saveActivity( $activity_id, $activity_def ){ - - $exists = \Activity::find( $activity_id ); - - //if the object activity exists, remove and update with recent - if( $exists ){ - \Activity::where('_id', $activity_id)->delete(); - } - - //save record - \Activity::insert( - array('_id' => $activity_id, - 'definition' => $activity_def) - ); - - } - - public function getActivity( $activity_id ){ - return \Activity::where('_id', $activity_id)->first(); - } - -} \ No newline at end of file diff --git a/app/locker/repository/RepositoryServiceProvider.php b/app/locker/repository/RepositoryServiceProvider.php index 1cfbe5638b..6adfd48a5b 100644 --- a/app/locker/repository/RepositoryServiceProvider.php +++ b/app/locker/repository/RepositoryServiceProvider.php @@ -11,8 +11,8 @@ public function register(){ 'Locker\Repository\User\EloquentUserRepository' ); $this->app->bind( - 'Locker\Repository\Statement\StatementRepository', - 'Locker\Repository\Statement\EloquentStatementRepository' + 'Locker\Repository\Statement\Repository', + 'Locker\Repository\Statement\EloquentRepository' ); $this->app->bind( 'Locker\Repository\Lrs\Repository', @@ -34,10 +34,6 @@ public function register(){ 'Locker\Repository\Document\DocumentRepository', 'Locker\Repository\Document\EloquentDocumentRepository' ); - $this->app->bind( - 'Locker\Repository\Activity\ActivityRepository', - 'Locker\Repository\Activity\EloquentActivityRepository' - ); $this->app->bind( 'Locker\Repository\OAuthApp\OAuthAppRepository', 'Locker\Repository\OAuthApp\EloquentOAuthAppRepository' From 7cc62121df05dc193656468b9c11d5622c997370 Mon Sep 17 00:00:00 2001 From: Ryan Smith <0ryansmith1994@gmail.com> Date: Mon, 20 Apr 2015 10:11:23 +0100 Subject: [PATCH 18/34] Splits statement controller. --- .../xapi/StatementIndexController.php | 122 ++++++++++++++ .../xapi/StatementStoreController.php | 150 ++++++++++++++++++ 2 files changed, 272 insertions(+) create mode 100644 app/controllers/xapi/StatementIndexController.php create mode 100644 app/controllers/xapi/StatementStoreController.php diff --git a/app/controllers/xapi/StatementIndexController.php b/app/controllers/xapi/StatementIndexController.php new file mode 100644 index 0000000000..fcf2ba86ad --- /dev/null +++ b/app/controllers/xapi/StatementIndexController.php @@ -0,0 +1,122 @@ +statements = $statement_repo; + } + + /** + * Gets an array of statements. + * https://github.com/adlnet/xAPI-Spec/blob/master/xAPI.md#723-getstatements + * @param String $lrs_id + * @return Response + */ + public function index($lrs_id) { + // Gets an index of the statements with the given options. + list($statements, $count, $opts) = $this->statements->index(array_merge([ + 'lrs_id' => $lrs_id, + 'langs' => LockerRequest::header('Accept-Language', []) + ], LockerRequest::all())); + + // Defines the content type and body of the response. + if ($opts['attachments'] === true) { + $content_type = 'multipart/mixed; boundary='.static::BOUNDARY; + $body = $this->makeAttachmentsResult($statements, $count, $opts); + } else { + $content_type = 'application/json;'; + $body = $this->makeStatementsResult($statements, $count, $opts); + } + + // Creates the response. + return \Response::make($body, 200, [ + 'Content-Type' => $content_type, + 'X-Experience-API-Consistent-Through' => Helpers::getCurrentDate() + ]);; + } + + /** + * Makes a statements result. + * @param [\stdClass] $statements + * @param Int $count + * @param [String => Mixed] $opts + * @return \stdClass + */ + private function makeStatementsResult(array $statements, $count, array $opts) { + // Defaults to empty array of statements. + $statements = $statements ?: []; + + // Replaces '&46;' in keys with '.' in statements. + // http://docs.learninglocker.net/docs/installation#quirks + $statements = Helpers::replaceHtmlEntity($statements); + + // Creates the statement result. + $statement_result = (object) [ + 'more' => $this->getMoreLink($count, $options['limit'], $options['offset']), + 'statements' => $statements + ]; + + return json_encode($statement_result); + } + + /** + * Makes an attachments result. + * @param [\stdClass] $statements + * @param [String => Mixed] $opts + * @return \stdClass + */ + private function makeAttachmentsResult(array $statements, $count, array $opts) { + $content_type = 'multipart/mixed; boundary='.static::BOUNDARY; + $statement_result = "Content-Type:application/json{static::EOL}{static::EOL}".$this->makeStatementsResult( + $statements, + $count, + $opts + ); + $body = "--{static::BOUNDARY}{static::EOL}".implode( + "{static::EOL}--{static::BOUNDARY}{static::EOL}", + array_merge([$statement_result], array_map(function ($attachment) { + return ( + 'Content-Type:'.$attachment->content_type.static::EOL. + 'Content-Transfer-Encoding:binary'.static::EOL. + 'X-Experience-API-Hash:'.$attachment->hash. + static::EOL.static::EOL. + $attachment->content + ); + }, $this->statements->getAttachments($statements, $opts))) + )."{static::EOL}--{static::BOUNDARY}--"; + } + + private function getMoreLink($count, $limit, $offset) { + // Calculates the $next_offset. + $next_offset = $offset + $limit; + if ($total <= $next_offset) return ''; + + // Changes (when defined) or appends (when undefined) offset. + $query = \Request::getQueryString(); + $statement_route = \URL::route('xapi.statement', [], false); + $current_url = $query ? $statement_route.'?'.$query : $statement_route; + + if (strpos($query, "offset=$offset") !== false) { + return str_replace( + 'offset=' . $offset, + 'offset=' . $next_offset, + $current_url + ); + } else { + $separator = strpos($current_url, '?') !== False ? '&' : '?'; + return $current_url . $separator . 'offset=' . $next_offset; + } + } +} diff --git a/app/controllers/xapi/StatementStoreController.php b/app/controllers/xapi/StatementStoreController.php new file mode 100644 index 0000000000..ceca7aadad --- /dev/null +++ b/app/controllers/xapi/StatementStoreController.php @@ -0,0 +1,150 @@ +statements = $statement_repo; + } + + /** + * Deals with multipart requests. + * @return ['content' => $content, 'attachments' => $attachments]. + */ + private function getParts() { + $content = \LockerRequest::getContent(); + $contentType = \LockerRequest::header('content-type'); + $types = explode(';', $contentType, 2); + $mimeType = count($types) >= 1 ? $types[0] : $types; + + if ($mimeType == 'multipart/mixed') { + $components = Attachments::setAttachments($contentType, $content); + + // Returns 'formatting' error. + if (empty($components)) { + throw new Exceptions\Exception('There is a problem with the formatting of your submitted content.'); + } + + // Returns 'no attachment' error. + if (!isset($components['attachments'])) { + throw new Exceptions\Exception('There were no attachments.'); + } + + $content = $components['body']; + $attachments = $components['attachments']; + } else { + $attachments = []; + } + + return [ + 'content' => $content, + 'attachments' => $attachments + ]; + } + + /** + * Stores (POSTs) a newly created statement in storage. + * @param String $lrs_id + * @return Response + */ + public function store($lrs_id) { + Helpers::validateAtom(new XApiImt(LockerRequest::header('Content-Type'))); + + if (LockerRequest::hasParam(StatementController::STATEMENT_ID)) { + throw new Exceptions\Exception('Statement ID parameter is invalid.'); + } + + return IlluminateResponse::json($this->createStatements($lrs_id), 200, Helpers::getCORSHeaders()); + } + + /** + * Updates (PUTs) Statement with the given id. + * @param String $lrs_id + * @return Response + */ + protected function update($lrs_id) { + Helpers::validateAtom(new XApiImt(LockerRequest::header('Content-Type'))); + + $this->createStatements($lrs_id, function ($statements) { + $statement_id = \LockerRequest::getParam(StatementController::STATEMENT_ID); + + // Returns a error if identifier is not present. + if (!$statement_id) { + throw new Exceptions\Exception('A statement ID is required to PUT.'); + } + + // Adds the ID to the statement. + $statements[0]->id = $statement_id; + return $statements; + }); + + return IlluminateResponse::make('', 204, Helpers::getCORSHeaders()); + } + + /** + * Creates statements from the content of the request. + * @param String $lrs_id + * @param Callable|null $modifier A function that modifies the statements before storing them. + * @return AssocArray Result of storing the statements. + */ + private function createStatements($lrs_id, Callable $modifier = null) { + // Gets parts of the request. + $parts = $this->getParts(); + $content = $parts['content']; + + // Decodes $statements from $content. + $statements = json_decode($content); + if ($statements === null && $content != 'null' && $content != '') { + throw new Exceptions\Exception('Invalid JSON'); + } + + // Ensures that $statements is an array. + if (!is_array($statements)) { + $statements = [$statements]; + } + + // Runs the modifier if there is one and there are statements. + if (count($statements) > 0 && $modifier !== null) { + $statements = $modifier($statements); + } + + // Saves $statements with attachments. + return $this->statements->store( + $statements, + is_array($parts['attachments']) ? $parts['attachments'] : [], + [ + 'lrs_id' => $lrs_id, + 'authority' => $this->getAuthority() + ] + ); + } + + private function getAuthority() { + $client = (new \Client) + ->where('api.basic_key', \LockerRequest::getUser()) + ->where('api.basic_secret', \LockerRequest::getPassword()) + ->first(); + + if ($client != null && isset($client['authority'])) { + return json_decode(json_encode($client['authority'])); + } else { + $site = \Site::first(); + return (object) [ + 'name' => $site->name, + 'mbox' => 'mailto:' . $site->email, + 'objectType' => 'Agent' + ]; + } + } +} From bc60a34964ad2168fc1aeaaf16a49db468676a26 Mon Sep 17 00:00:00 2001 From: Ryan Smith <0ryansmith1994@gmail.com> Date: Mon, 20 Apr 2015 10:12:19 +0100 Subject: [PATCH 19/34] Removes old failing tests. --- app/tests/ActivityTest.php | 33 ------- app/tests/ApiV1QueryAnalyticsTest.php | 122 -------------------------- app/tests/AuthorityTest.php | 49 ----------- app/tests/LrsTest.php | 107 ---------------------- 4 files changed, 311 deletions(-) delete mode 100644 app/tests/ActivityTest.php delete mode 100644 app/tests/ApiV1QueryAnalyticsTest.php delete mode 100644 app/tests/AuthorityTest.php delete mode 100644 app/tests/LrsTest.php diff --git a/app/tests/ActivityTest.php b/app/tests/ActivityTest.php deleted file mode 100644 index 2168d5d17d..0000000000 --- a/app/tests/ActivityTest.php +++ /dev/null @@ -1,33 +0,0 @@ -_id = \Locker\Helpers\Helpers::getRandomValue(); - $activity->definition = array( - 'type' => \Locker\Helpers\Helpers::getRandomValue(), - 'name' => array( - 'en-US' => \Locker\Helpers\Helpers::getRandomValue() - ), - 'description' => array( - 'en-US' => \Locker\Helpers\Helpers::getRandomValue() - ) - ); - $result = $activity->save(); - $this->assertTrue($result); - - // Load activity from db - $aid = $activity->_id; - $db_activity = Activity::find($aid); - $this->assertEquals($db_activity->_id, $activity->_id); - - // Delete activity - $db_activity->delete(); - $this->assertEquals(Activity::find($aid), NULL); - } -} - diff --git a/app/tests/ApiV1QueryAnalyticsTest.php b/app/tests/ApiV1QueryAnalyticsTest.php deleted file mode 100644 index f89dd6166d..0000000000 --- a/app/tests/ApiV1QueryAnalyticsTest.php +++ /dev/null @@ -1,122 +0,0 @@ - 'quan@ll.com']); - Auth::login($user); - $this->createLRS(); - - // Creates testing statements. - $vs = json_decode(file_get_contents(__DIR__ . '/Fixtures/Analytics.json'), true); - $statement = App::make('Locker\Repository\Statement\EloquentStatementRepository'); - $statement->create([json_decode(json_encode($vs))], $this->lrs); - - $vs2 = $vs; - $vs2['object']['definition']['type'] = 'http://activitystrea.ms/schema/2.0/badge'; - - $statement2 = App::make('Locker\Repository\Statement\EloquentStatementRepository'); - $statement2->create([json_decode(json_encode($vs2))], $this->lrs); - } - - private function callResponse($params = [], $lrs) { - $auth = [ - 'PHP_AUTH_USER' => $lrs->api['basic_key'], - 'PHP_AUTH_PW' => $lrs->api['basic_secret'] - ]; - $route = '/api/v1/query/analytics'; - - return $this->call('GET', $route, $params, [], $auth); - } - - public function testDefaultQuery() { - $response = $this->callResponse([], $this->lrs); - $data = $response->getData(); - $this->assertEquals($data->version, 'v1'); - $this->assertEquals($data->route, 'api/v1/query/analytics'); - } - - public function testTimeQuery() { - $response = $this->callResponse(['type' => 'time'], $this->lrs); - $data = $response->getData()->data; - $this->assertEquals($data[0]->count, 2); - } - - public function testUserQuery() { - $response = $this->callResponse(['type' => 'user'], $this->lrs); - - $data = $response->getData()->data; - $checkTypeUser = TRUE; - foreach ($data as $value) { - if (!in_array($value->data->name, array('quanvm', 'quanvm2'))) { - $checkTypeUser = FALSE; - } - } - $this->assertTRUE($checkTypeUser); - } - - public function testVerbQuery() { - $response = $this->callResponse(['type' => 'verb'], $this->lrs); - $data = $response->getData()->data; - $this->assertEquals($data[0]->data->id, "http://adlnet.gov/expapi/verbs/experienced"); - } - - public function testDayQuery() { - $intervalLrs = Lrs::find('536b02d4c01f1325618b4567'); - if ($intervalLrs) { - $response = $this->callResponse(['interval' => 'Day'], $intervalLrs); - $data = $response->getData()->data; - $this->assertEquals(count($data), 2); - } - } - - public function testMonthQuery() { - $intervalLrs = Lrs::find('536b03bbc01f13a6618b4567'); - if ($intervalLrs) { - $response = $this->callResponse(['interval' => 'Month'], $intervalLrs); - $data = $response->getData()->data; - $this->assertEquals(count($data), 2); - } - } - - public function testYearQuery() { - $intervalLrs = Lrs::find('536b05ccc01f1392638b4567'); - if ($intervalLrs) { - $response = $this->callResponse(['interval' => 'Year'], $intervalLrs); - $data = $response->getData()->data; - $this->assertEquals(count($data), 2); - } - } - - public function testSinceQuery() { - $response = $this->callResponse(['since' => date('Y-m-d')], $this->lrs); - $data = $response->getData()->data; - $this->assertEquals($data[0]->count, 2); - } - - public function testEmptySinceQuery() { - $date = date('Y-m-d', strtotime("+1 day")); - $response = $this->callResponse(['since' => $date], $this->lrs); - $data = $response->getData()->data; - $this->assertTrue(empty($data)); - } - - public function testUntilQuery() { - $date = date('Y-m-d', strtotime("+1 day")); - $response = $this->callResponse(['until' => $date], $this->lrs); - $data = $response->getData()->data; - $this->assertEquals($data[0]->count, 2); - } - - public function testEmptyUntilQuery() { - $date = date('Y-m-d', strtotime("-1 day")); - $response = $this->callResponse(['until' => $date], $this->lrs); - $data = $response->getData()->data; - $this->assertTrue(empty($data)); - } -} diff --git a/app/tests/AuthorityTest.php b/app/tests/AuthorityTest.php deleted file mode 100644 index eaa7de93f7..0000000000 --- a/app/tests/AuthorityTest.php +++ /dev/null @@ -1,49 +0,0 @@ - $this->dummyEmail())); - Auth::login($user); - $this->lrs = $this->createLRS(); - $this->statement = App::make('Locker\Repository\Statement\EloquentStatementRepository'); - } - - public function testAuthority() - { - $stmt = $this->defaultStatment(); - - // Ensure if authority is empty the LRS will be create anonymous authoriry - $authority = $stmt['authority']; - unset($stmt['authority']); - $return = $this->createStatement($stmt, $this->lrs); - $this->assertEquals(gettype($return), 'array'); - - $stmt_id = reset($return); - $obj_stmt = $this->statement->show($this->lrs->_id, $stmt_id)->first(); - $stmt_authority = $obj_stmt->statement['authority']; - $this->assertTrue(!empty($stmt_authority)); - - // Ensure authority stored in db is same value with statment send to LRS - $stmt['authority'] = $authority; - $return = $this->createStatement($stmt, $this->lrs); - $stmt_id = reset($return); - $obj_stmt = $this->statement->show($this->lrs->_id, $stmt_id)->first(); - - $stmt_authority = $obj_stmt->statement['authority']; - $this->assertEquals($authority, $stmt_authority); - } - -} diff --git a/app/tests/LrsTest.php b/app/tests/LrsTest.php deleted file mode 100644 index defd20a775..0000000000 --- a/app/tests/LrsTest.php +++ /dev/null @@ -1,107 +0,0 @@ - 'quan@ll.com']); - Auth::login($user); - } - - /** - * Test LRS - */ - public function testLRS() - { - $lrs = new Lrs; - - // Test title required. - $values = array( - 'title' => '', - 'description' => \Locker\Helpers\Helpers::getRandomValue(), - 'api' => array('basic_key' => \Locker\Helpers\Helpers::getRandomValue(), - 'basic_secret' => \Locker\Helpers\Helpers::getRandomValue()) - ); - $validator = $lrs->validate($values); - $this->assertTrue($validator->fails()); - $this->assertFalse($validator->passes()); - - $values['title'] = \Locker\Helpers\Helpers::getRandomValue(); - $validator = $lrs->validate($values); - $this->assertTrue($validator->passes()); - - // Validate auth_service - - $values['auth_service_url'] = 'http://' . \Locker\Helpers\Helpers::getRandomValue() . '.adurolms.com'; - $validator = $lrs->validate($values); - $this->assertTrue($validator->passes()); - - // Add new lrs - $lrs->title = $values['title']; - $lrs->description = $values['description']; - $lrs->api = $values['api']; - $result = $lrs->save(); - $this->assertTrue($result); - - // Load lrs from db - $lrs_id = $lrs->_id; - $db_lrs = Lrs::find($lrs_id); - $this->assertEquals($db_lrs->_id, $lrs->_id); - - // Edit lrs - $title = \Locker\Helpers\Helpers::getRandomValue(); - $db_lrs->title = $title; - $db_lrs->save(); - $this->assertEquals($db_lrs->title, $title); - - // Delete lrs - $db_lrs->delete(); - $this->assertEquals(Lrs::find($lrs_id), NULL, 'delete lrs'); - } - - public function testInternalAuthentication() - { - $this->createLRS(); - - //create client for Auth Service - $auth = [ - 'api_key' => $this->lrs->api['basic_key'], - 'api_secret' => $this->lrs->api['basic_secret'], - ]; - - $response = $this->_makeRequest($auth); - $this->assertEquals($response->getStatusCode(), 200); - } - - public function testAuthenticationService() - { - $this->createLRS(); - - //create client for Auth Service - $auth = [ - 'api_key' => $this->lrs->api['basic_key'], - 'api_secret' => $this->lrs->api['basic_secret'], - ]; - - // Make sure response data for the get request - $response = $this->_makeRequest($auth, ['auth_type' => 'central']); - $this->assertEquals($response->getStatusCode(), 200); - } - - private function _makeRequest($auth, $param = []) - { - return $this->call("GET", '/data/xAPI/statements', $param, [], $this->makeRequestHeaders($auth)); - } - - public function testEnpoint() - { - $this->assertTrue(true); - } - -} From 1cb7830af3987e866f9b814685817c091466593d Mon Sep 17 00:00:00 2001 From: Ryan Smith <0ryansmith1994@gmail.com> Date: Mon, 20 Apr 2015 10:12:37 +0100 Subject: [PATCH 20/34] Removes example test. --- app/tests/ExampleTest.php | 17 ----------------- 1 file changed, 17 deletions(-) delete mode 100644 app/tests/ExampleTest.php diff --git a/app/tests/ExampleTest.php b/app/tests/ExampleTest.php deleted file mode 100644 index 39c122901a..0000000000 --- a/app/tests/ExampleTest.php +++ /dev/null @@ -1,17 +0,0 @@ -client->request('GET', '/'); - - $this->assertTrue($this->client->getResponse()->isOk()); - } - -} From a1e66cc1770a925a32d54125b90cec0ff94bced9 Mon Sep 17 00:00:00 2001 From: Ryan Smith <0ryansmith1994@gmail.com> Date: Mon, 20 Apr 2015 10:12:58 +0100 Subject: [PATCH 21/34] Improves testing for statement repo. --- .../Repos/Statement/EloquentIndexerTest.php | 16 ++++++++++------ app/tests/Repos/Statement/EloquentLinkerTest.php | 16 ++++++++++++++++ 2 files changed, 26 insertions(+), 6 deletions(-) create mode 100644 app/tests/Repos/Statement/EloquentLinkerTest.php diff --git a/app/tests/Repos/Statement/EloquentIndexerTest.php b/app/tests/Repos/Statement/EloquentIndexerTest.php index e286dbed71..5622c407d0 100644 --- a/app/tests/Repos/Statement/EloquentIndexerTest.php +++ b/app/tests/Repos/Statement/EloquentIndexerTest.php @@ -31,12 +31,11 @@ public function testFormat() { $this->assertEquals('Illuminate\Database\Eloquent\Collection', get_class($result)); $this->assertEquals(count($this->statements), $result->count()); $result->each(function ($statement) { - $this->assertEquals(true, is_array($statement)); - $this->assertEquals(true, isset($statement['id'])); - $this->assertEquals(true, is_string($statement['id'])); - $expected_statement = $this->getStatement()['statement']; - $expected_statement->id = $statement['id']; - $this->assertEquals(json_encode($expected_statement), json_encode($statement)); + $this->assertEquals(true, is_object($statement)); + $this->assertEquals(true, isset($statement->id)); + $this->assertEquals(true, is_string($statement->id)); + $expected_statement = $this->statements[0]->statement; + $this->assertStatementMatch($expected_statement, $statement); }); } @@ -50,4 +49,9 @@ public function testCount() { $this->assertEquals(true, is_int($result)); $this->assertEquals(count($this->statements), $result); } + + protected function assertStatementMatch(\stdClass $statement_a, \stdClass $statement_b) { + unset($statement_b->version); + $this->assertEquals(true, $statement_a == $statement_b); + } } diff --git a/app/tests/Repos/Statement/EloquentLinkerTest.php b/app/tests/Repos/Statement/EloquentLinkerTest.php new file mode 100644 index 0000000000..f1108f2146 --- /dev/null +++ b/app/tests/Repos/Statement/EloquentLinkerTest.php @@ -0,0 +1,16 @@ +linker = new Linker(); + } + + public function test() { + return null; + } +} From 3c62c01ecbcd6fa4442e4c54222468a8d5cc1d1b Mon Sep 17 00:00:00 2001 From: Ryan Smith <0ryansmith1994@gmail.com> Date: Mon, 20 Apr 2015 13:45:39 +0100 Subject: [PATCH 22/34] Improves tests. --- app/tests/API/StatementsTest.php | 155 --------- app/tests/API/TestCase.php | 108 ------ app/tests/InstanceTestCase.php | 41 +++ app/tests/LrsTestCase.php | 39 +++ app/tests/StatementRefTest.php | 324 +++++++++--------- app/tests/StatementsTestCase.php | 57 +++ app/tests/TestCase.php | 160 +-------- app/tests/{API => routes}/ExportsTest.php | 9 +- app/tests/routes/QueryTest.php | 119 +++++++ app/tests/{API => routes}/ReportsTest.php | 16 +- .../ResourcesTestCase.php} | 24 +- app/tests/routes/RouteTestTrait.php | 21 ++ app/tests/routes/StatementsTest.php | 156 +++++++++ 13 files changed, 627 insertions(+), 602 deletions(-) delete mode 100644 app/tests/API/StatementsTest.php delete mode 100644 app/tests/API/TestCase.php create mode 100644 app/tests/InstanceTestCase.php create mode 100644 app/tests/LrsTestCase.php create mode 100644 app/tests/StatementsTestCase.php rename app/tests/{API => routes}/ExportsTest.php (83%) create mode 100644 app/tests/routes/QueryTest.php rename app/tests/{API => routes}/ReportsTest.php (51%) rename app/tests/{API/ResourcesTest.php => routes/ResourcesTestCase.php} (76%) create mode 100644 app/tests/routes/RouteTestTrait.php create mode 100644 app/tests/routes/StatementsTest.php diff --git a/app/tests/API/StatementsTest.php b/app/tests/API/StatementsTest.php deleted file mode 100644 index 199f6964f7..0000000000 --- a/app/tests/API/StatementsTest.php +++ /dev/null @@ -1,155 +0,0 @@ -pipeline ?: file_get_contents(__DIR__ . '/../Fixtures/pipeline.json'); - } - - protected function requestStatementsAPI($method = 'GET', $url = '', $params = []) { - $server = $this->getHeaders($this->lrs->api); - Route::enableFilters(); - return $this->call($method, $url, $params, [], $server, ''); - } - - public function testAggregate() { - $response = $this->requestStatementsAPI('GET', static::$endpoint.'/aggregate', [ - 'pipeline' => $this->getPipeline() - ]); - - // Checks that the response is correct. - $this->assertEquals(200, $response->getStatusCode(), 'Incorrect status code.'); - $this->assertEquals(true, method_exists($response, 'getContent'), 'Incorrect response.'); - - // Checks that the content is correct. - $content = json_decode($response->getContent()); - $this->assertEquals(true, is_object($content), 'Incorrect content type.'); - $this->assertEquals(true, isset($content->result), 'No result.'); - $this->assertEquals(true, is_array($content->result), 'Incorrect result type.'); - $this->assertEquals(static::$statements, count($content->result), 'Incorrect number of results.'); - $this->assertEquals(true, is_object($content->result[0]), 'Incorrect projection type.'); - $this->assertEquals(true, isset($content->result[0]->statement), 'No statement.'); - $this->assertEquals(true, is_object($content->result[0]->statement), 'Incorrect statement type.'); - $this->assertEquals(true, isset($content->result[0]->statement->actor), 'No actor.'); - $this->assertEquals(true, isset($content->ok), 'No ok.'); - $this->assertEquals(true, is_numeric($content->ok), 'Incorrect ok type.'); - $this->assertEquals(1, $content->ok, 'Incorrect ok.'); - } - - public function testAggregateTime() { - $response = $this->requestStatementsAPI('GET', static::$endpoint.'/aggregate/time', [ - 'match' => '{"active": true}' - ]); - - // Checks that the response is correct. - $this->assertEquals(200, $response->getStatusCode(), 'Incorrect status code.'); - $this->assertEquals(true, method_exists($response, 'getContent'), 'Incorrect response.'); - - // Checks that the content is correct. - $content = json_decode($response->getContent()); - $this->assertEquals(true, is_object($content), 'Incorrect content type.'); - $this->assertEquals(true, isset($content->result), 'No result.'); - $this->assertEquals(true, is_array($content->result), 'Incorrect result type.'); - $this->assertEquals(1, count($content->result), 'Incorrect number of results.'); - $this->assertEquals(true, is_object($content->result[0]), 'Incorrect projection type.'); - $this->assertEquals(true, isset($content->result[0]->count), 'No count.'); - $this->assertEquals(true, is_numeric($content->result[0]->count), 'Incorrect count type.'); - $this->assertEquals(static::$statements, is_numeric($content->result[0]->count), 'Incorrect count.'); - $this->assertEquals(true, isset($content->result[0]->date), 'No date.'); - $this->assertEquals(true, is_array($content->result[0]->date), 'Incorrect date type.'); - $this->assertEquals(true, isset($content->ok), 'No ok.'); - $this->assertEquals(true, is_numeric($content->ok), 'Incorrect ok type.'); - $this->assertEquals(1, $content->ok, 'Incorrect ok.'); - } - - public function testAggregateObject() { - $response = $this->requestStatementsAPI('GET', static::$endpoint.'/aggregate/object', [ - 'match' => '{"active": true}' - ]); - - // Checks that the response is correct. - $this->assertEquals(200, $response->getStatusCode(), 'Incorrect status code.'); - $this->assertEquals(true, method_exists($response, 'getContent'), 'Incorrect response.'); - - // Checks that the content is correct. - $content = json_decode($response->getContent()); - $this->assertEquals(true, is_object($content), 'Incorrect content type.'); - $this->assertEquals(true, isset($content->result), 'No result.'); - $this->assertEquals(true, is_array($content->result), 'Incorrect result type.'); - $this->assertEquals(1, count($content->result), 'Incorrect number of results.'); - $this->assertEquals(true, is_object($content->result[0]), 'Incorrect projection type.'); - $this->assertEquals(true, isset($content->result[0]->count), 'No count.'); - $this->assertEquals(true, is_numeric($content->result[0]->count), 'Incorrect count type.'); - $this->assertEquals(static::$statements, is_numeric($content->result[0]->count), 'Incorrect count.'); - $this->assertEquals(true, isset($content->result[0]->data), 'No data.'); - $this->assertEquals(true, is_array($content->result[0]->data), 'Incorrect data type.'); - $this->assertEquals(static::$statements, count($content->result[0]->data), 'Incorrect data.'); - $this->assertEquals(true, isset($content->result[0]->data[0]), 'Incorrect data item.'); - $this->assertEquals(true, is_object($content->result[0]->data[0]), 'Incorrect data item type.'); - $this->assertEquals(true, isset($content->result[0]->data[0]->actor), 'No actor.'); - $this->assertEquals(true, is_object($content->result[0]->data[0]->actor), 'Incorrect actor type.'); - $this->assertEquals(true, isset($content->ok), 'No ok.'); - $this->assertEquals(true, is_numeric($content->ok), 'Incorrect ok type.'); - $this->assertEquals(1, $content->ok, 'Incorrect ok.'); - } - - public function testWhere() { - $response = $this->requestStatementsAPI('GET', static::$endpoint.'/where', [ - 'filter' => '[[{"active", true}]]', - 'limit' => 1, - 'page' => 1 - ]); - - // Checks that the response is correct. - $this->assertEquals(200, $response->getStatusCode(), 'Incorrect status code.'); - $this->assertEquals(true, method_exists($response, 'getContent'), 'Incorrect response.'); - - // Checks that the content is correct. - $content = json_decode($response->getContent()); - $this->assertEquals(true, is_object($content), 'Incorrect content type.'); - - // Checks set props. - $this->assertEquals(true, isset($content->total), 'No total.'); - $this->assertEquals(true, isset($content->per_page), 'No per_page.'); - $this->assertEquals(true, isset($content->current_page), 'No current_page.'); - $this->assertEquals(true, isset($content->last_page), 'No last_page.'); - $this->assertEquals(true, isset($content->from), 'No from.'); - $this->assertEquals(true, isset($content->to), 'No to.'); - $this->assertEquals(true, isset($content->data), 'No data.'); - - // Checks prop types. - $this->assertEquals(true, is_numeric($content->total), 'Incorrect total type.'); - $this->assertEquals(true, is_numeric($content->per_page), 'Incorrect per_page type.'); - $this->assertEquals(true, is_numeric($content->current_page), 'Incorrect current_page type.'); - $this->assertEquals(true, is_numeric($content->last_page), 'Incorrect last_page type.'); - $this->assertEquals(true, is_numeric($content->from), 'Incorrect from type.'); - $this->assertEquals(true, is_numeric($content->to), 'Incorrect to type.'); - $this->assertEquals(true, is_array($content->data), 'Incorrect data type.'); - - // Checks prop content. - $this->assertEquals(static::$statements, $content->total, 'Incorrect total value.'); - $this->assertEquals(1, $content->per_page, 'Incorrect per_page value.'); - $this->assertEquals(1, $content->current_page, 'Incorrect current_page value.'); - $this->assertEquals(static::$statements, $content->last_page, 'Incorrect last_page value.'); - $this->assertEquals(1, $content->from, 'Incorrect from value.'); - $this->assertEquals(1, $content->to, 'Incorrect to value.'); - $this->assertEquals(1, count($content->data), 'Incorrect data count.'); - $this->assertEquals(true, isset($content->data[0]), 'No data item.'); - $this->assertEquals(true, is_object($content->data[0]), 'Incorrect data item type.'); - $this->assertEquals(true, isset($content->data[0]->statement), 'No statement.'); - $this->assertEquals(true, is_object($content->data[0]->statement), 'Incorrect statement type.'); - $this->assertEquals(true, isset($content->data[0]->statement->actor), 'No actor.'); - $this->assertEquals(true, is_object($content->data[0]->statement->actor), 'Incorrect actor type.'); - } - - public function tearDown() { - parent::tearDown(); - } -} diff --git a/app/tests/API/TestCase.php b/app/tests/API/TestCase.php deleted file mode 100644 index 351d54a182..0000000000 --- a/app/tests/API/TestCase.php +++ /dev/null @@ -1,108 +0,0 @@ -user = $this->createUser(); - Auth::login($this->user); - $this->lrs = $this->createLRS(); - $this->createStatements(); - } - - public function createApplication() { - $unitTesting = true; - $testEnvironment = 'testing'; - return require __DIR__ . '/../../../bootstrap/start.php'; - } - - protected function createUser() { - $user = User::firstOrCreate(['email' => 'test@example.com']); - - // If first user, create site object - if (User::count() === 1) { - $site = new Site([ - 'name' => 'Test Site', - 'email' => $user->email, - 'lang' => 'en-US', - 'create_lrs' => ['super'], - 'registration' => 'Closed', - 'restrict' => 'None', - 'domain' => '', - 'super' => [['user' => $user->_id]], - ]); - $site->save(); - } - return $user; - } - - protected function createLRS() { - $lrs = new Lrs([ - 'title' => 'TestLRS', - 'api' => [ - 'basic_key' => Helpers::getRandomValue(), - 'basic_secret' => Helpers::getRandomValue() - ], - 'owner' => [ - '_id' => Auth::user()->_id, - ], - 'users' => [[ - '_id' => Auth::user()->_id, - 'email' => Auth::user()->email, - 'name' => Auth::user()->name, - 'role' => 'admin' - ]], - 'domain' => '', - ]); - - $lrs->save(); - - // Hack header request - $_SERVER['SERVER_NAME'] = $lrs->title . '.com.vn'; - return $lrs; - } - - protected function createStatements() { - $statement = $this->getStatement(); - for ($i = 0; $i < static::$statements; $i += 1) { - $response = $this->requestAPI('POST', '/data/xAPI/statements', $statement); - } - } - - protected function getHeaders($auth) { - return [ - 'PHP_AUTH_USER' => $auth['basic_key'], - 'PHP_AUTH_PW' => $auth['basic_secret'] - ]; - } - - protected function requestAPI($method = 'GET', $url = '', $content = '') { - $server = $this->getHeaders($this->lrs->api); - $server['HTTP_X-Experience-API-Version'] = '1.0.1'; - Route::enableFilters(); - return $this->call($method, $url, [], [], $server, $content); - } - - protected function getStatement() { - return file_get_contents(__DIR__ . '/../Fixtures/statement.json'); - } - - public function tearDown() { - Statement::where('lrs._id', $this->lrs->_id)->delete(); - $this->lrs->delete(); - $this->user->delete(); - parent::tearDown(); - } -} diff --git a/app/tests/InstanceTestCase.php b/app/tests/InstanceTestCase.php new file mode 100644 index 0000000000..3577925d45 --- /dev/null +++ b/app/tests/InstanceTestCase.php @@ -0,0 +1,41 @@ +user = $this->createUser(); + $this->site = $this->createSite($this->user); + } + + protected function createSite(\User $user) { + $model = new \Site([ + 'name' => 'Test', + 'description' => '', + 'email' => $user->email, + 'lang' => 'en-US', + 'create_lrs' => 'super', + 'registration' => 'Closed', + 'restrict' => 'None', + 'domain' => '', + 'super' => [['user' => $user->_id]] + ]); + $model->save(); + return $model; + } + + protected function createUser() { + $model = new \User([ + 'email' => 'test@example.com' + ]); + $model->save(); + return $model; + } + + public function tearDown() { + $this->site->delete(); + $this->user->delete(); + parent::tearDown(); + } +} diff --git a/app/tests/LrsTestCase.php b/app/tests/LrsTestCase.php new file mode 100644 index 0000000000..095fc94564 --- /dev/null +++ b/app/tests/LrsTestCase.php @@ -0,0 +1,39 @@ +lrs = $this->createLrs($this->user); + $_SERVER['SERVER_NAME'] = $this->lrs->title.'.com.vn'; + } + + protected function createLrs(\User $user) { + $model = new \Lrs([ + 'title' => Helpers::getRandomValue(), + 'description' => Helpers::getRandomValue(), + 'subdomain' => Helpers::getRandomValue(), + 'api' => [ + 'basic_key' => helpers::getRandomValue(), + 'basic_secret' => helpers::getRandomValue() + ], + 'owner' => ['_id' => $user->_id], + 'users' => Helpers::getRandomValue(), + 'users' => [[ + '_id' => $user->_id, + 'email' => $user->email, + 'name' => $user->name, + 'role' => 'admin' + ]] + ]); + $model->save(); + return $model; + } + + public function tearDown() { + $this->lrs->delete(); + parent::tearDown(); + } +} diff --git a/app/tests/StatementRefTest.php b/app/tests/StatementRefTest.php index c8b849dc57..49885f32d4 100644 --- a/app/tests/StatementRefTest.php +++ b/app/tests/StatementRefTest.php @@ -1,162 +1,162 @@ - 'test@example.com']); - Auth::login($user); - $this->createLrs(); - } - - private function sendStatements($statements) { - $auth = [ - 'api_key' => $this->lrs->api['basic_key'], - 'api_secret' => $this->lrs->api['basic_secret'], - ]; - $headers = $this->makeRequestHeaders($auth); - $statements = json_encode($statements); - return $this->call('POST', '/data/xAPI/statements', [], [], $headers, $statements); - } - - protected function generateStatement($statement) { - return array_merge($statement, [ - 'actor' => [ - 'mbox' => 'mailto:test@example.com' - ], - 'verb' => [ - 'id' => 'http://www.example.com/verbs/test', - ], - ]); - } - - private function createReferenceStatement($reference_id, $statement = []) { - return $this->generateStatement(array_merge($statement, [ - 'object' => [ - 'objectType' => 'StatementRef', - 'id' => $this->generateUUID($reference_id) - ] - ])); - } - - private function createIdStatement($id, $statement = []) { - return $this->generateStatement(array_merge($statement, [ - 'id' => $this->generateUUID($id) - ])); - } - - private function checkStatement($id, $expected_references = [], $expected_referrers = []) { - $uuid = $this->generateUUID($id); - $statement = \Statement::where('lrs._id', $this->lrs->_id)->where('statement.id', '=', $uuid)->first(); - - //$queries = DB::getQueryLog(); - - $expected_references = array_map(function ($ref) { - return $this->generateUUID($ref); - }, $expected_references); - - $expected_referrers = array_map(function ($ref) { - return $this->generateUUID($ref); - }, $expected_referrers); - - // Checks $expected_references. - $references = array_map(function ($ref) { - return $ref['id']; - }, isset($statement->refs) ? $statement->refs : []); - - // Checks $expected_referrers. - $referrers = (new \Statement) - ->select('statement.id') - ->where('statement.object.id', '=', $uuid) - ->where('statement.object.objectType', '=', 'StatementRef') - ->get()->toArray(); - $referrers = array_map(function ($ref) { - return $ref['statement']['id']; - }, $referrers); - - $this->assertEmpty(array_diff($expected_referrers, $referrers)); - } - - private function generateUUID($id) { - $len = strlen($id); - $start = str_repeat('0', 8 - $len); - return $id . $start . '-0000-0000-b000-000000000000'; - } - - public function testInsert1() { - $this->sendStatements([ - $this->createIdStatement('A', $this->createReferenceStatement('E')) - ]); - - $this->checkStatement('A', [], []); - } - - public function testInsert2() { - $this->sendStatements([ - $this->createIdStatement('A', $this->createReferenceStatement('E')) - ]); - - $this->sendStatements([ - $this->createIdStatement('C', $this->createReferenceStatement('A')), - $this->createIdStatement('D', $this->createReferenceStatement('B')) - ]); - - $this->checkStatement('A', [], ['C']); - $this->checkStatement('C', ['A'], []); - $this->checkStatement('D', [], []); - } - - public function testInsert3() { - $this->sendStatements([ - $this->createIdStatement('A', $this->createReferenceStatement('E')) - ]); - - $this->sendStatements([ - $this->createIdStatement('C', $this->createReferenceStatement('A')), - $this->createIdStatement('D', $this->createReferenceStatement('B')) - ]); - - $this->sendStatements([ - $this->createIdStatement('B', $this->createReferenceStatement('A')) - ]); - - $this->checkStatement('A', [], ['B', 'C']); - $this->checkStatement('B', ['A'], ['D']); - $this->checkStatement('C', ['A'], []); - $this->checkStatement('D', ['B', 'A'], []); - } - - public function testInsert4() { - $this->sendStatements([ - $this->createIdStatement('A', $this->createReferenceStatement('E')) - ]); - - $this->sendStatements([ - $this->createIdStatement('C', $this->createReferenceStatement('A')), - $this->createIdStatement('D', $this->createReferenceStatement('B')) - ]); - - $this->sendStatements([ - $this->createIdStatement('B', $this->createReferenceStatement('A')) - ]); - - $this->sendStatements([ - $this->createIdStatement('E', $this->createReferenceStatement('D')) - ]); - - $this->checkStatement('A', ['E', 'D', 'B'], ['B', 'C']); - $this->checkStatement('B', ['A', 'E', 'D'], ['D']); - $this->checkStatement('C', ['A', 'E', 'D', 'B'], []); - $this->checkStatement('D', ['B', 'A', 'E'], ['E']); - $this->checkStatement('E', ['D', 'B', 'A'], ['A']); - } - - public function tearDown() { - parent::tearDown(); - if ($this->lrs) $this->lrs->delete(); - } - -} + 'test@example.com']); +// \Auth::login($user); +// $this->createLrs(); +// } + +// private function sendStatements($statements) { +// $auth = [ +// 'api_key' => $this->lrs->api['basic_key'], +// 'api_secret' => $this->lrs->api['basic_secret'], +// ]; +// $headers = $this->makeRequestHeaders($auth); +// $statements = json_encode($statements); +// return $this->call('POST', '/data/xAPI/statements', [], [], $headers, $statements); +// } + +// protected function generateStatement($statement) { +// return array_merge($statement, [ +// 'actor' => [ +// 'mbox' => 'mailto:test@example.com' +// ], +// 'verb' => [ +// 'id' => 'http://www.example.com/verbs/test', +// ], +// ]); +// } + +// private function createReferenceStatement($reference_id, $statement = []) { +// return $this->generateStatement(array_merge($statement, [ +// 'object' => [ +// 'objectType' => 'StatementRef', +// 'id' => $this->generateUUID($reference_id) +// ] +// ])); +// } + +// private function createIdStatement($id, $statement = []) { +// return $this->generateStatement(array_merge($statement, [ +// 'id' => $this->generateUUID($id) +// ])); +// } + +// private function checkStatement($id, $expected_references = [], $expected_referrers = []) { +// $uuid = $this->generateUUID($id); +// $statement = \Statement::where('lrs._id', $this->lrs->_id)->where('statement.id', '=', $uuid)->first(); + +// //$queries = DB::getQueryLog(); + +// $expected_references = array_map(function ($ref) { +// return $this->generateUUID($ref); +// }, $expected_references); + +// $expected_referrers = array_map(function ($ref) { +// return $this->generateUUID($ref); +// }, $expected_referrers); + +// // Checks $expected_references. +// $references = array_map(function ($ref) { +// return $ref['id']; +// }, isset($statement->refs) ? $statement->refs : []); + +// // Checks $expected_referrers. +// $referrers = (new \Statement) +// ->select('statement.id') +// ->where('statement.object.id', '=', $uuid) +// ->where('statement.object.objectType', '=', 'StatementRef') +// ->get()->toArray(); +// $referrers = array_map(function ($ref) { +// return $ref['statement']['id']; +// }, $referrers); + +// $this->assertEmpty(array_diff($expected_referrers, $referrers)); +// } + +// private function generateUUID($id) { +// $len = strlen($id); +// $start = str_repeat('0', 8 - $len); +// return $id . $start . '-0000-0000-b000-000000000000'; +// } + +// public function testInsert1() { +// $this->sendStatements([ +// $this->createIdStatement('A', $this->createReferenceStatement('E')) +// ]); + +// $this->checkStatement('A', [], []); +// } + +// public function testInsert2() { +// $this->sendStatements([ +// $this->createIdStatement('A', $this->createReferenceStatement('E')) +// ]); + +// $this->sendStatements([ +// $this->createIdStatement('C', $this->createReferenceStatement('A')), +// $this->createIdStatement('D', $this->createReferenceStatement('B')) +// ]); + +// $this->checkStatement('A', [], ['C']); +// $this->checkStatement('C', ['A'], []); +// $this->checkStatement('D', [], []); +// } + +// public function testInsert3() { +// $this->sendStatements([ +// $this->createIdStatement('A', $this->createReferenceStatement('E')) +// ]); + +// $this->sendStatements([ +// $this->createIdStatement('C', $this->createReferenceStatement('A')), +// $this->createIdStatement('D', $this->createReferenceStatement('B')) +// ]); + +// $this->sendStatements([ +// $this->createIdStatement('B', $this->createReferenceStatement('A')) +// ]); + +// $this->checkStatement('A', [], ['B', 'C']); +// $this->checkStatement('B', ['A'], ['D']); +// $this->checkStatement('C', ['A'], []); +// $this->checkStatement('D', ['B', 'A'], []); +// } + +// public function testInsert4() { +// $this->sendStatements([ +// $this->createIdStatement('A', $this->createReferenceStatement('E')) +// ]); + +// $this->sendStatements([ +// $this->createIdStatement('C', $this->createReferenceStatement('A')), +// $this->createIdStatement('D', $this->createReferenceStatement('B')) +// ]); + +// $this->sendStatements([ +// $this->createIdStatement('B', $this->createReferenceStatement('A')) +// ]); + +// $this->sendStatements([ +// $this->createIdStatement('E', $this->createReferenceStatement('D')) +// ]); + +// $this->checkStatement('A', ['E', 'D', 'B'], ['B', 'C']); +// $this->checkStatement('B', ['A', 'E', 'D'], ['D']); +// $this->checkStatement('C', ['A', 'E', 'D', 'B'], []); +// $this->checkStatement('D', ['B', 'A', 'E'], ['E']); +// $this->checkStatement('E', ['D', 'B', 'A'], ['A']); +// } + +// public function tearDown() { +// parent::tearDown(); +// if ($this->lrs) $this->lrs->delete(); +// } + +// } diff --git a/app/tests/StatementsTestCase.php b/app/tests/StatementsTestCase.php new file mode 100644 index 0000000000..1eabbc5061 --- /dev/null +++ b/app/tests/StatementsTestCase.php @@ -0,0 +1,57 @@ +statements = [$this->createStatement( + $this->lrs, + $this->generateStatement() + )]; + } + + protected function generateStatement($statement = []) { + $timestamp = Helpers::getCurrentDate(); + return [ + 'actor' => [ + 'mbox' => 'mailto:test@example.com', + 'objectType' => 'Agent' + ], + 'verb' => [ + 'id' => 'http://www.example.com/verbs/test' + ], + 'object' => [ + 'id' => 'http://www.example.com/objects/test', + 'objectType' => 'Activity' + ], + 'timestamp' => $timestamp, + 'stored' => $timestamp, + 'authority' => [ + 'mbox' => 'mailto:test@example.com', + 'objectType' => 'Agent' + ] + ]; + } + + protected function createStatement(\Lrs $lrs, array $statement) { + $model = new \Statement(array_merge([ + 'lrs' => ['_id' => $lrs->_id], + 'statement' => $statement, + 'active' => true, + 'voided' => false, + 'refs' => [] + ], $statement)); + $model->timestamp = new \MongoDate(strtotime($model->statement['timestamp'])); + $model->save(); + return $model; + } + + public function tearDown() { + array_map(function ($statement) { + $statement->delete(); + }, $this->statements); + parent::tearDown(); + } +} diff --git a/app/tests/TestCase.php b/app/tests/TestCase.php index 4e791cb2e9..c39d6bab66 100644 --- a/app/tests/TestCase.php +++ b/app/tests/TestCase.php @@ -1,168 +1,16 @@ - 'quan@ll.com']); - //if first user, create site object - if (\User::count() == 1) { - $site = new \Site; - $site->name = 'Test'; - $site->description = ''; - $site->email = $user->email; - $site->lang = 'en-US'; - $site->create_lrs = array('super'); - $site->registration = 'Closed'; - $site->restrict = 'None'; //restrict registration to a specific email domain - $site->domain = ''; - $site->super = array(array('user' => $user->_id)); - $site->save(); - } - } - - /** - * Create dummy LRS - * @return \Lrs - */ - protected function createLRS() - { - $lrs = new Lrs; - $lrs->title = helpers::getRandomValue(); - $lrs->description = helpers::getRandomValue(); - $lrs->subdomain = helpers::getRandomValue(); - $lrs->api = array( - 'basic_key' => helpers::getRandomValue(), - 'basic_secret' => helpers::getRandomValue() - ); - - // $lrs->auth_service = property_exists($this, 'lrsAuthMethod') ? $this->lrsAuthMethod : Lrs::INTERNAL_LRS; - // $lrs->auth_service_url = property_exists($this, 'auth_service_url') ? - // $this->auth_service_url : ''; - // $lrs->token = 'our-token'; - - $lrs->owner = array('_id' => Auth::user()->_id); - $lrs->users = array( - array('_id' => Auth::user()->_id, - 'email' => Auth::user()->email, - 'name' => Auth::user()->name, - 'role' => 'admin' - ) - ); - - $lrs->save(); - $this->lrs = $lrs; - - // Hack header request - $_SERVER['SERVER_NAME'] = $this->lrs->title . '.com.vn'; - return $lrs; - } - - /** - * Return default statement data. - */ - protected function defaultStatment() - { - $siteAttrs = \Site::first(); - - return [ - 'actor' => [ - 'objectType' => 'Agent', - 'mbox' => 'mailto:duy.nguyen@go1.com.au', - 'name' => 'duynguyen' - ], - 'verb' => [ - "id" => "http://adlnet.gov/expapi/verbs/experienced", - "display" => ["und" => "experienced"] - ], - 'context' => [ - "contextActivities" => [ - "parent" => [[ - "id" => "http://tincanapi.com/GolfExample_TCAPI", - "objectType" => "Activity" - ]], - "grouping" => [[ - "id" => "http://tincanapi.com/GolfExample_TCAPI", - "objectType" => "Activity" - ]] - ] - ], - "object" => [ - "id" => "http://tincanapi.com/GolfExample_TCAPI/Playing/Scoring.html", - "objectType" => "Activity", - "definition" => [ - "name" => [ - "en-US" => "Scoring" - ], - "description" => [ - "en-US" => "An overview of how to score a round of golf." - ] - ] - ], - "authority" => [ - "name" => $siteAttrs->name, - "mbox" => "mailto:" . $siteAttrs->email, - "objectType" => "Agent" - ], - ]; - } - - /** - * Create dummy statement with lrs. - * - * @param Lrs $lrs - * @return type - */ - protected function createStatement($statement, $lrs) - { - return App::make('Locker\Repository\Statement\EloquentStatementRepository') - ->create([json_decode(json_encode($statement))], $lrs); - } - - /** - * Create dummy Auth Client - * @param type $lrs - * @return type - */ - protected function createClientAuth($auth) - { - return [ - 'name' => helpers::getRandomValue(), - 'api_key' => $auth['api_key'], - 'api_secret' => $auth['api_secret'], - ]; - } - - protected function dummyEmail() - { - return helpers::getRandomValue() . '@go1.com.au'; - } - - protected function makeRequestHeaders($auth, $version="1.0.1") - { - return [ - 'PHP_AUTH_USER' => $auth['api_key'], - 'PHP_AUTH_PW' => $auth['api_secret'], - 'HTTP_X-Experience-API-Version' => $version - ]; - } - } diff --git a/app/tests/API/ExportsTest.php b/app/tests/routes/ExportsTest.php similarity index 83% rename from app/tests/API/ExportsTest.php rename to app/tests/routes/ExportsTest.php index 2355ae025d..02a529380e 100644 --- a/app/tests/API/ExportsTest.php +++ b/app/tests/routes/ExportsTest.php @@ -1,5 +1,4 @@ - 'Test report', 'description' => 'Test report description', 'query' => [ @@ -35,7 +34,7 @@ protected function createReport() { } public function testJson() { - $response = $this->requestAPI('GET', static::$endpoint.'/'."{$this->model->_id}/show"); + $response = $this->requestResource('GET', static::$endpoint.'/'."{$this->model->_id}/show"); $content = $response->getContent(); // Checks that the response is correct. @@ -43,7 +42,7 @@ public function testJson() { } public function testCsv() { - $response = $this->requestAPI('GET', static::$endpoint.'/'."{$this->model->_id}/show/csv"); + $response = $this->requestResource('GET', static::$endpoint.'/'."{$this->model->_id}/show/csv"); $content = $response->getContent(); // Checks that the response is correct. diff --git a/app/tests/routes/QueryTest.php b/app/tests/routes/QueryTest.php new file mode 100644 index 0000000000..055aee6d89 --- /dev/null +++ b/app/tests/routes/QueryTest.php @@ -0,0 +1,119 @@ +request('GET', 'api/v1/query/analytics', $params, $this->getServer($this->lrs), $content); + } + + public function testAnalyticsDefault() { + $response = $this->requestAnalytics(); + $data = $response->getData(); + $this->assertEquals($data->version, 'v1'); + $this->assertEquals($data->route, 'api/v1/query/analytics'); + } + + public function testAnalyticsTime() { + $response = $this->requestAnalytics(['type' => 'time']); + $data = $response->getData()->data; + $this->assertEquals(count($this->statements), $data[0]->count); + } + + public function testUserQuery() { + $response = $this->requestAnalytics(['type' => 'user']); + $data = $response->getData(); + + $this->assertEquals(true, is_object($data)); + $this->assertEquals(true, isset($data->data)); + $this->assertEquals(true, is_array($data->data)); + $this->assertEquals(count($this->statements), count($data->data)); + + // Asserts correct properties on data[0]. + $this->assertEquals(true, isset($data->data[0]->count)); + $this->assertEquals(true, isset($data->data[0]->dates)); + $this->assertEquals(true, isset($data->data[0]->data)); + + // Asserts correct property types on data[0]. + $this->assertEquals(true, is_numeric($data->data[0]->count)); + $this->assertEquals(true, is_array($data->data[0]->dates)); + $this->assertEquals(true, is_object($data->data[0]->data)); + + // Asserts correct values on data[0]. + $this->assertEquals(count($this->statements), $data->data[0]->count); + $this->assertEquals(count($this->statements), count($data->data[0]->dates)); + + // Asserts correct properties on data[0]->data. + $this->assertEquals(true, isset($data->data[0]->data->mbox)); + $this->assertEquals(true, isset($data->data[0]->data->objectType)); + + // Asserts correct property types on data[0]->data. + $this->assertEquals(true, is_string($data->data[0]->data->mbox)); + $this->assertEquals(true, is_string($data->data[0]->data->objectType)); + + // Asserts correct property values on data[0]->data. + $this->assertEquals('mailto:test@example.com', $data->data[0]->data->mbox); + $this->assertEquals('Agent', $data->data[0]->data->objectType); + } + + public function testAnalyticsVerb() { + $response = $this->requestAnalytics(['type' => 'verb']); + $data = $response->getData()->data; + $this->assertEquals($data[0]->data->id, 'http://www.example.com/verbs/test'); + } + + public function testAnalyticsDay() { + $response = $this->requestAnalytics(['interval' => 'day']); + $data = $response->getData()->data; + $this->assertEquals(count($this->statements), count($data)); + } + + public function testAnalyticsMonth() { + $response = $this->requestAnalytics(['interval' => 'month']); + $data = $response->getData()->data; + $this->assertEquals(count($this->statements), count($data)); + } + + public function testAnalyticsYear() { + $response = $this->requestAnalytics(['interval' => 'year']); + $data = $response->getData()->data; + $this->assertEquals(count($this->statements), count($data)); + } + + public function testAnalyticsSince() { + $date = date('Y-m-d', strtotime("-1 day")); + $response = $this->requestAnalytics(['since' => $date]); + $data = $response->getData()->data; + $this->assertEquals(count($this->statements), $data[0]->count); + } + + public function testAnalyticsEmptySince() { + $date = date('Y-m-d', strtotime("+1 day")); + $response = $this->requestAnalytics(['since' => $date]); + $data = $response->getData()->data; + $this->assertEquals(true, empty($data)); + } + + public function testAnalyticsUntil() { + $date = date('Y-m-d', strtotime("+1 day")); + $response = $this->requestAnalytics(['until' => $date]); + $data = $response->getData()->data; + $this->assertEquals(count($this->statements), $data[0]->count); + } + + public function testAnalyticsEmptyUntil() { + $date = date('Y-m-d', strtotime("-1 day")); + $response = $this->requestAnalytics(['until' => $date]); + $data = $response->getData()->data; + $this->assertEquals(true, empty($data)); + } + + public function tearDown() { + parent::tearDown(); + } +} diff --git a/app/tests/API/ReportsTest.php b/app/tests/routes/ReportsTest.php similarity index 51% rename from app/tests/API/ReportsTest.php rename to app/tests/routes/ReportsTest.php index da9509a636..862560a43f 100644 --- a/app/tests/API/ReportsTest.php +++ b/app/tests/routes/ReportsTest.php @@ -1,4 +1,4 @@ -requestAPI('GET', static::$endpoint.'/'."{$this->model->_id}/run"); + $response = $this->requestResource('GET', static::$endpoint.'/'."{$this->model->_id}/run"); $content = $this->getContentFromResponse($response); // Checks that the response is correct. - $this->assertEquals(self::$statements, count($content), 'Incorrect amount of statements.'); - $this->assertEquals(200, $response->getStatusCode(), 'Incorrect status code.'); + $this->assertEquals(count($this->statements), count($content)); + $this->assertEquals(200, $response->getStatusCode()); } public function testGraph() { - $response = $this->requestAPI('GET', static::$endpoint.'/'."{$this->model->_id}/graph"); + $response = $this->requestResource('GET', static::$endpoint.'/'."{$this->model->_id}/graph"); $content = $this->getContentFromResponse($response); // Checks that the response is correct. - $this->assertEquals(true, isset($content[0]['count']), 'Incorrectly formed content.'); - $this->assertEquals(self::$statements, $content[0]['count'], 'Incorrect amount of statements.'); - $this->assertEquals(200, $response->getStatusCode(), 'Incorrect status code.'); + $this->assertEquals(true, isset($content[0]['count'])); + $this->assertEquals(count($this->statements), $content[0]['count']); + $this->assertEquals(200, $response->getStatusCode()); } } diff --git a/app/tests/API/ResourcesTest.php b/app/tests/routes/ResourcesTestCase.php similarity index 76% rename from app/tests/API/ResourcesTest.php rename to app/tests/routes/ResourcesTestCase.php index e74d627776..3d398d25e0 100644 --- a/app/tests/API/ResourcesTest.php +++ b/app/tests/routes/ResourcesTestCase.php @@ -1,7 +1,10 @@ -request($method, $uri, $params, $this->getServer($this->lrs), $content); + } + public function testIndex() { - $response = $this->requestAPI('GET', static::$endpoint); + $response = $this->requestResource('GET', static::$endpoint); $content = $this->getContentFromResponse($response); $model = $this->getModelFromContent($content[0]); @@ -35,7 +43,7 @@ public function testIndex() { } public function testStore() { - $response = $this->requestAPI('POST', static::$endpoint, json_encode($this->data)); + $response = $this->requestResource('POST', static::$endpoint, json_encode($this->data)); $model = $this->getModelFromResponse($response); // Checks that the response is correct. @@ -45,7 +53,7 @@ public function testStore() { public function testUpdate() { $data = array_merge($this->data, $this->update); - $response = $this->requestAPI('PUT', static::$endpoint.'/'.$this->model->_id, json_encode($data)); + $response = $this->requestResource('PUT', static::$endpoint.'/'.$this->model->_id, json_encode($data)); $model = $this->getModelFromResponse($response); // Checks that the response is correct. @@ -54,7 +62,7 @@ public function testUpdate() { } public function testShow() { - $response = $this->requestAPI('GET', static::$endpoint.'/'.$this->model->_id); + $response = $this->requestResource('GET', static::$endpoint.'/'.$this->model->_id); $model = $this->getModelFromResponse($response); // Checks that the response is correct. @@ -63,7 +71,7 @@ public function testShow() { } public function testDestroy() { - $response = $this->requestAPI('DELETE', static::$endpoint.'/'.$this->model->_id); + $response = $this->requestResource('DELETE', static::$endpoint.'/'.$this->model->_id); $content = $this->getContentFromResponse($response); // Checks that the response is correct. @@ -87,7 +95,7 @@ protected function getContentFromResponse(JsonResponse $response) { } public function tearDown() { - //$this->model->delete(); + $this->model->delete(); parent::tearDown(); } } diff --git a/app/tests/routes/RouteTestTrait.php b/app/tests/routes/RouteTestTrait.php new file mode 100644 index 0000000000..0bd89961ec --- /dev/null +++ b/app/tests/routes/RouteTestTrait.php @@ -0,0 +1,21 @@ + $lrs->api['basic_key'], + 'PHP_AUTH_PW' => $lrs->api['basic_secret'], + 'HTTP_X-Experience-API-Version' => $version + ]; + } + + protected function request($method = 'GET', $uri = '', $params = [], $server = [], $content = null) { + $files = []; + $changeHistory = true; + \Route::enableFilters(); + + // http://laravel.com/api/4.2/Illuminate/Foundation/Testing/TestCase.html#method_call + return $this->call($method, $uri, $params, $files, $server, $content, $changeHistory); + } +} diff --git a/app/tests/routes/StatementsTest.php b/app/tests/routes/StatementsTest.php new file mode 100644 index 0000000000..505b0161fd --- /dev/null +++ b/app/tests/routes/StatementsTest.php @@ -0,0 +1,156 @@ +getServer($this->lrs); + return $this->request($method, $uri, $params, $server, $content); + } + + public function testAggregate() { + $response = $this->requestStatements('aggregate', [ + 'pipeline' => $this->getPipeline() + ]); + + // Checks that the response is correct. + $this->assertEquals(200, $response->getStatusCode()); + $this->assertEquals(true, method_exists($response, 'getContent')); + + // Checks that the content is correct. + $content = json_decode($response->getContent()); + $this->assertEquals(true, is_object($content)); + $this->assertEquals(true, isset($content->result)); + $this->assertEquals(true, is_array($content->result)); + $this->assertEquals(count($this->statements), count($content->result)); + $this->assertEquals(true, is_object($content->result[0])); + $this->assertEquals(true, isset($content->result[0]->statement)); + $this->assertEquals(true, is_object($content->result[0]->statement)); + $this->assertEquals(true, isset($content->result[0]->statement->actor)); + $this->assertEquals(true, isset($content->ok)); + $this->assertEquals(true, is_numeric($content->ok)); + $this->assertEquals(1, $content->ok); + } + + public function testAggregateTime() { + $response = $this->requestStatements('aggregate/time', [ + 'match' => '{"active": true}' + ]); + + // Checks that the response is correct. + $this->assertEquals(200, $response->getStatusCode()); + $this->assertEquals(true, method_exists($response, 'getContent')); + + // Checks that the content is correct. + $content = json_decode($response->getContent()); + $this->assertEquals(true, is_object($content)); + $this->assertEquals(true, isset($content->result)); + $this->assertEquals(true, is_array($content->result)); + $this->assertEquals(1, count($content->result)); + $this->assertEquals(true, is_object($content->result[0])); + $this->assertEquals(true, isset($content->result[0]->count)); + $this->assertEquals(true, is_numeric($content->result[0]->count)); + $this->assertEquals(count($this->statements), is_numeric($content->result[0]->count)); + $this->assertEquals(true, isset($content->result[0]->date)); + $this->assertEquals(true, is_array($content->result[0]->date)); + $this->assertEquals(true, isset($content->ok)); + $this->assertEquals(true, is_numeric($content->ok)); + $this->assertEquals(1, $content->ok); + } + + public function testAggregateObject() { + $response = $this->requestStatements('aggregate/object', [ + 'match' => '{"active": true}' + ]); + + // Checks that the response is correct. + $this->assertEquals(200, $response->getStatusCode()); + $this->assertEquals(true, method_exists($response, 'getContent')); + + // Checks that the content is correct. + $content = json_decode($response->getContent()); + $this->assertEquals(true, is_object($content)); + $this->assertEquals(true, isset($content->result)); + $this->assertEquals(true, is_array($content->result)); + $this->assertEquals(1, count($content->result)); + $this->assertEquals(true, is_object($content->result[0])); + $this->assertEquals(true, isset($content->result[0]->count)); + $this->assertEquals(true, is_numeric($content->result[0]->count)); + $this->assertEquals(count($this->statements), is_numeric($content->result[0]->count)); + $this->assertEquals(true, isset($content->result[0]->data)); + $this->assertEquals(true, is_array($content->result[0]->data)); + $this->assertEquals(count($this->statements), count($content->result[0]->data)); + $this->assertEquals(true, isset($content->result[0]->data[0])); + $this->assertEquals(true, is_object($content->result[0]->data[0])); + $this->assertEquals(true, isset($content->result[0]->data[0]->actor)); + $this->assertEquals(true, is_object($content->result[0]->data[0]->actor)); + $this->assertEquals(true, isset($content->ok)); + $this->assertEquals(true, is_numeric($content->ok)); + $this->assertEquals(1, $content->ok); + } + + public function testWhere() { + $response = $this->requestStatements('where', [ + 'filter' => '[[{"active", true}]]', + 'limit' => 1, + 'page' => 1 + ]); + + // Checks that the response is correct. + $this->assertEquals(200, $response->getStatusCode()); + $this->assertEquals(true, method_exists($response, 'getContent')); + + // Checks that the content is correct. + $content = json_decode($response->getContent()); + $this->assertEquals(true, is_object($content)); + + // Checks set props. + $this->assertEquals(true, isset($content->total)); + $this->assertEquals(true, isset($content->per_page)); + $this->assertEquals(true, isset($content->current_page)); + $this->assertEquals(true, isset($content->last_page)); + $this->assertEquals(true, isset($content->from)); + $this->assertEquals(true, isset($content->to)); + $this->assertEquals(true, isset($content->data)); + + // Checks prop types. + $this->assertEquals(true, is_numeric($content->total)); + $this->assertEquals(true, is_numeric($content->per_page)); + $this->assertEquals(true, is_numeric($content->current_page)); + $this->assertEquals(true, is_numeric($content->last_page)); + $this->assertEquals(true, is_numeric($content->from)); + $this->assertEquals(true, is_numeric($content->to)); + $this->assertEquals(true, is_array($content->data)); + + // Checks prop content. + $this->assertEquals(count($this->statements), $content->total); + $this->assertEquals(1, $content->per_page); + $this->assertEquals(1, $content->current_page); + $this->assertEquals(count($this->statements), $content->last_page); + $this->assertEquals(1, $content->from); + $this->assertEquals(1, $content->to); + $this->assertEquals(1, count($content->data)); + $this->assertEquals(true, isset($content->data[0])); + $this->assertEquals(true, is_object($content->data[0])); + $this->assertEquals(true, isset($content->data[0]->statement)); + $this->assertEquals(true, is_object($content->data[0]->statement)); + $this->assertEquals(true, isset($content->data[0]->statement->actor)); + $this->assertEquals(true, is_object($content->data[0]->statement->actor)); + } + + public function tearDown() { + parent::tearDown(); + } +} From d24b7ca062172b05c7f4a8cfa0a7ac21cb7c644f Mon Sep 17 00:00:00 2001 From: Ryan Smith <0ryansmith1994@gmail.com> Date: Mon, 20 Apr 2015 13:58:13 +0100 Subject: [PATCH 23/34] Removes unnecessary fixtures. --- app/tests/Fixtures/Analytics.json | 41 ------------------------------- app/tests/Fixtures/pipeline.json | 1 - 2 files changed, 42 deletions(-) delete mode 100644 app/tests/Fixtures/Analytics.json delete mode 100644 app/tests/Fixtures/pipeline.json diff --git a/app/tests/Fixtures/Analytics.json b/app/tests/Fixtures/Analytics.json deleted file mode 100644 index 2ec09a1543..0000000000 --- a/app/tests/Fixtures/Analytics.json +++ /dev/null @@ -1,41 +0,0 @@ -{ - "actor": { - "objectType": "Agent", - "mbox": "mailto:duy.nguyen@go1.com.au", - "name": "quanvm" - }, - "verb": { - "id": "http://adlnet.gov/expapi/verbs/experienced", - "display": {"und": "experienced"} - }, - "context": { - "contextActivities": { - "parent": [{ - "id": "http://tincanapi.com/GolfExample_TCAPI", - "objectType": "Activity" - }], - "grouping": [{ - "id": "http://tincanapi.com/GolfExample_TCAPI", - "objectType": "Activity" - }] - } - }, - "object": { - "id": "http://tincanapi.com/GolfExample_TCAPI/Playing/Scoring.html", - "objectType": "Activity", - "definition": { - "name": { - "en-US": "Scoring" - }, - "description": { - "en-US": "An overview of how to score a round of golf." - }, - "type": "http://activitystrea.ms/schema/1.0/badge" - } - }, - "authority": { - "name": "", - "mbox": "mailto:quan@ll.com", - "objectType": "Agent" - } -} \ No newline at end of file diff --git a/app/tests/Fixtures/pipeline.json b/app/tests/Fixtures/pipeline.json deleted file mode 100644 index dcbeec71d0..0000000000 --- a/app/tests/Fixtures/pipeline.json +++ /dev/null @@ -1 +0,0 @@ -[{"$match":{"active":true}},{"$project":{"_id":0,"statement":1}}] From c40702d5855e3d9b6b56ffbcfa801c9ce86de3c4 Mon Sep 17 00:00:00 2001 From: Ryan Smith <0ryansmith1994@gmail.com> Date: Mon, 20 Apr 2015 14:00:35 +0100 Subject: [PATCH 24/34] Uses lower case directory names. --- .../{Fixtures => ffixtures}/Repos/StatementFormatter1.json | 0 app/tests/{Fixtures => ffixtures}/statement.json | 0 app/tests/{Repos => rrepos}/Statement/EloquentIndexerTest.php | 0 app/tests/{Repos => rrepos}/Statement/EloquentLinkerTest.php | 0 app/tests/{Repos => rrepos}/Statement/EloquentShowerTest.php | 0 app/tests/{Repos => rrepos}/Statement/EloquentTest.php | 2 +- app/tests/{Repos => rrepos}/Statement/FormatterTest.php | 4 ++-- app/tests/{Repos => rrepos}/Statement/IndexOptionsTest.php | 0 app/tests/{Repos => rrepos}/Statement/OptionsTest.php | 0 app/tests/{Repos => rrepos}/Statement/ShowOptionsTest.php | 0 app/tests/{Repos => rrepos}/Statement/StoreOptionsTest.php | 0 11 files changed, 3 insertions(+), 3 deletions(-) rename app/tests/{Fixtures => ffixtures}/Repos/StatementFormatter1.json (100%) rename app/tests/{Fixtures => ffixtures}/statement.json (100%) rename app/tests/{Repos => rrepos}/Statement/EloquentIndexerTest.php (100%) rename app/tests/{Repos => rrepos}/Statement/EloquentLinkerTest.php (100%) rename app/tests/{Repos => rrepos}/Statement/EloquentShowerTest.php (100%) rename app/tests/{Repos => rrepos}/Statement/EloquentTest.php (97%) rename app/tests/{Repos => rrepos}/Statement/FormatterTest.php (97%) rename app/tests/{Repos => rrepos}/Statement/IndexOptionsTest.php (100%) rename app/tests/{Repos => rrepos}/Statement/OptionsTest.php (100%) rename app/tests/{Repos => rrepos}/Statement/ShowOptionsTest.php (100%) rename app/tests/{Repos => rrepos}/Statement/StoreOptionsTest.php (100%) diff --git a/app/tests/Fixtures/Repos/StatementFormatter1.json b/app/tests/ffixtures/Repos/StatementFormatter1.json similarity index 100% rename from app/tests/Fixtures/Repos/StatementFormatter1.json rename to app/tests/ffixtures/Repos/StatementFormatter1.json diff --git a/app/tests/Fixtures/statement.json b/app/tests/ffixtures/statement.json similarity index 100% rename from app/tests/Fixtures/statement.json rename to app/tests/ffixtures/statement.json diff --git a/app/tests/Repos/Statement/EloquentIndexerTest.php b/app/tests/rrepos/Statement/EloquentIndexerTest.php similarity index 100% rename from app/tests/Repos/Statement/EloquentIndexerTest.php rename to app/tests/rrepos/Statement/EloquentIndexerTest.php diff --git a/app/tests/Repos/Statement/EloquentLinkerTest.php b/app/tests/rrepos/Statement/EloquentLinkerTest.php similarity index 100% rename from app/tests/Repos/Statement/EloquentLinkerTest.php rename to app/tests/rrepos/Statement/EloquentLinkerTest.php diff --git a/app/tests/Repos/Statement/EloquentShowerTest.php b/app/tests/rrepos/Statement/EloquentShowerTest.php similarity index 100% rename from app/tests/Repos/Statement/EloquentShowerTest.php rename to app/tests/rrepos/Statement/EloquentShowerTest.php diff --git a/app/tests/Repos/Statement/EloquentTest.php b/app/tests/rrepos/Statement/EloquentTest.php similarity index 97% rename from app/tests/Repos/Statement/EloquentTest.php rename to app/tests/rrepos/Statement/EloquentTest.php index 5d19298252..84229c3875 100644 --- a/app/tests/Repos/Statement/EloquentTest.php +++ b/app/tests/rrepos/Statement/EloquentTest.php @@ -38,7 +38,7 @@ protected function createStatement($id) { protected function getStatement() { return [ - 'statement' => json_decode(file_get_contents(__DIR__ . '../../../Fixtures/statement.json')), + 'statement' => json_decode(file_get_contents(__DIR__ . '../../../fixtures/statement.json')), 'active' => true, 'voided' => false, 'refs' => [], diff --git a/app/tests/Repos/Statement/FormatterTest.php b/app/tests/rrepos/Statement/FormatterTest.php similarity index 97% rename from app/tests/Repos/Statement/FormatterTest.php rename to app/tests/rrepos/Statement/FormatterTest.php index cd6c1e4402..84524e258f 100644 --- a/app/tests/Repos/Statement/FormatterTest.php +++ b/app/tests/rrepos/Statement/FormatterTest.php @@ -21,7 +21,7 @@ private function cloneObj(\stdClass $obj) { } public function testIdentityStatements() { - $statement = json_decode(file_get_contents(__DIR__ . '/../../Fixtures/Repos/StatementFormatter1.json')); + $statement = json_decode(file_get_contents(__DIR__ . '/../../fixtures/Repos/StatementFormatter1.json')); $formatted = $this->formatter->identityStatement($this->cloneObj($statement)); $this->assertEquals(true, is_object($formatted)); @@ -54,7 +54,7 @@ public function testIdentityStatements() { } public function testCanonicalStatements() { - $statement = json_decode(file_get_contents(__DIR__ . '/../../Fixtures/Repos/StatementFormatter1.json')); + $statement = json_decode(file_get_contents(__DIR__ . '/../../fixtures/Repos/StatementFormatter1.json')); $formatted = $this->formatter->canonicalStatement($this->cloneObj($statement), ['en-GB']); $this->assertEquals(true, is_object($formatted)); diff --git a/app/tests/Repos/Statement/IndexOptionsTest.php b/app/tests/rrepos/Statement/IndexOptionsTest.php similarity index 100% rename from app/tests/Repos/Statement/IndexOptionsTest.php rename to app/tests/rrepos/Statement/IndexOptionsTest.php diff --git a/app/tests/Repos/Statement/OptionsTest.php b/app/tests/rrepos/Statement/OptionsTest.php similarity index 100% rename from app/tests/Repos/Statement/OptionsTest.php rename to app/tests/rrepos/Statement/OptionsTest.php diff --git a/app/tests/Repos/Statement/ShowOptionsTest.php b/app/tests/rrepos/Statement/ShowOptionsTest.php similarity index 100% rename from app/tests/Repos/Statement/ShowOptionsTest.php rename to app/tests/rrepos/Statement/ShowOptionsTest.php diff --git a/app/tests/Repos/Statement/StoreOptionsTest.php b/app/tests/rrepos/Statement/StoreOptionsTest.php similarity index 100% rename from app/tests/Repos/Statement/StoreOptionsTest.php rename to app/tests/rrepos/Statement/StoreOptionsTest.php From 808bcf932b81c8f3c3943b1bb4dedb69e9902838 Mon Sep 17 00:00:00 2001 From: Ryan Smith <0ryansmith1994@gmail.com> Date: Mon, 20 Apr 2015 14:01:06 +0100 Subject: [PATCH 25/34] Updates StatementRefTest.php --- app/tests/StatementRefTest.php | 162 -------------------------- app/tests/routes/StatementRefTest.php | 145 +++++++++++++++++++++++ 2 files changed, 145 insertions(+), 162 deletions(-) delete mode 100644 app/tests/StatementRefTest.php create mode 100644 app/tests/routes/StatementRefTest.php diff --git a/app/tests/StatementRefTest.php b/app/tests/StatementRefTest.php deleted file mode 100644 index 49885f32d4..0000000000 --- a/app/tests/StatementRefTest.php +++ /dev/null @@ -1,162 +0,0 @@ - 'test@example.com']); -// \Auth::login($user); -// $this->createLrs(); -// } - -// private function sendStatements($statements) { -// $auth = [ -// 'api_key' => $this->lrs->api['basic_key'], -// 'api_secret' => $this->lrs->api['basic_secret'], -// ]; -// $headers = $this->makeRequestHeaders($auth); -// $statements = json_encode($statements); -// return $this->call('POST', '/data/xAPI/statements', [], [], $headers, $statements); -// } - -// protected function generateStatement($statement) { -// return array_merge($statement, [ -// 'actor' => [ -// 'mbox' => 'mailto:test@example.com' -// ], -// 'verb' => [ -// 'id' => 'http://www.example.com/verbs/test', -// ], -// ]); -// } - -// private function createReferenceStatement($reference_id, $statement = []) { -// return $this->generateStatement(array_merge($statement, [ -// 'object' => [ -// 'objectType' => 'StatementRef', -// 'id' => $this->generateUUID($reference_id) -// ] -// ])); -// } - -// private function createIdStatement($id, $statement = []) { -// return $this->generateStatement(array_merge($statement, [ -// 'id' => $this->generateUUID($id) -// ])); -// } - -// private function checkStatement($id, $expected_references = [], $expected_referrers = []) { -// $uuid = $this->generateUUID($id); -// $statement = \Statement::where('lrs._id', $this->lrs->_id)->where('statement.id', '=', $uuid)->first(); - -// //$queries = DB::getQueryLog(); - -// $expected_references = array_map(function ($ref) { -// return $this->generateUUID($ref); -// }, $expected_references); - -// $expected_referrers = array_map(function ($ref) { -// return $this->generateUUID($ref); -// }, $expected_referrers); - -// // Checks $expected_references. -// $references = array_map(function ($ref) { -// return $ref['id']; -// }, isset($statement->refs) ? $statement->refs : []); - -// // Checks $expected_referrers. -// $referrers = (new \Statement) -// ->select('statement.id') -// ->where('statement.object.id', '=', $uuid) -// ->where('statement.object.objectType', '=', 'StatementRef') -// ->get()->toArray(); -// $referrers = array_map(function ($ref) { -// return $ref['statement']['id']; -// }, $referrers); - -// $this->assertEmpty(array_diff($expected_referrers, $referrers)); -// } - -// private function generateUUID($id) { -// $len = strlen($id); -// $start = str_repeat('0', 8 - $len); -// return $id . $start . '-0000-0000-b000-000000000000'; -// } - -// public function testInsert1() { -// $this->sendStatements([ -// $this->createIdStatement('A', $this->createReferenceStatement('E')) -// ]); - -// $this->checkStatement('A', [], []); -// } - -// public function testInsert2() { -// $this->sendStatements([ -// $this->createIdStatement('A', $this->createReferenceStatement('E')) -// ]); - -// $this->sendStatements([ -// $this->createIdStatement('C', $this->createReferenceStatement('A')), -// $this->createIdStatement('D', $this->createReferenceStatement('B')) -// ]); - -// $this->checkStatement('A', [], ['C']); -// $this->checkStatement('C', ['A'], []); -// $this->checkStatement('D', [], []); -// } - -// public function testInsert3() { -// $this->sendStatements([ -// $this->createIdStatement('A', $this->createReferenceStatement('E')) -// ]); - -// $this->sendStatements([ -// $this->createIdStatement('C', $this->createReferenceStatement('A')), -// $this->createIdStatement('D', $this->createReferenceStatement('B')) -// ]); - -// $this->sendStatements([ -// $this->createIdStatement('B', $this->createReferenceStatement('A')) -// ]); - -// $this->checkStatement('A', [], ['B', 'C']); -// $this->checkStatement('B', ['A'], ['D']); -// $this->checkStatement('C', ['A'], []); -// $this->checkStatement('D', ['B', 'A'], []); -// } - -// public function testInsert4() { -// $this->sendStatements([ -// $this->createIdStatement('A', $this->createReferenceStatement('E')) -// ]); - -// $this->sendStatements([ -// $this->createIdStatement('C', $this->createReferenceStatement('A')), -// $this->createIdStatement('D', $this->createReferenceStatement('B')) -// ]); - -// $this->sendStatements([ -// $this->createIdStatement('B', $this->createReferenceStatement('A')) -// ]); - -// $this->sendStatements([ -// $this->createIdStatement('E', $this->createReferenceStatement('D')) -// ]); - -// $this->checkStatement('A', ['E', 'D', 'B'], ['B', 'C']); -// $this->checkStatement('B', ['A', 'E', 'D'], ['D']); -// $this->checkStatement('C', ['A', 'E', 'D', 'B'], []); -// $this->checkStatement('D', ['B', 'A', 'E'], ['E']); -// $this->checkStatement('E', ['D', 'B', 'A'], ['A']); -// } - -// public function tearDown() { -// parent::tearDown(); -// if ($this->lrs) $this->lrs->delete(); -// } - -// } diff --git a/app/tests/routes/StatementRefTest.php b/app/tests/routes/StatementRefTest.php new file mode 100644 index 0000000000..6fc18e984c --- /dev/null +++ b/app/tests/routes/StatementRefTest.php @@ -0,0 +1,145 @@ +getServer($this->lrs); + return $this->request($method, $uri, $params, $server, $content); + } + + private function createReferenceStatement($reference_id, $statement = []) { + return $this->generateStatement(array_merge($statement, [ + 'object' => [ + 'objectType' => 'StatementRef', + 'id' => $this->generateUUID($reference_id) + ] + ])); + } + + private function createIdStatement($id, $statement = []) { + return $this->generateStatement(array_merge($statement, [ + 'id' => $this->generateUUID($id) + ])); + } + + private function checkStatement($id, $expected_references = [], $expected_referrers = []) { + $uuid = $this->generateUUID($id); + $statement = \Statement::where('lrs._id', $this->lrs->_id)->where('statement.id', '=', $uuid)->first(); + + //$queries = DB::getQueryLog(); + + $expected_references = array_map(function ($ref) { + return $this->generateUUID($ref); + }, $expected_references); + + $expected_referrers = array_map(function ($ref) { + return $this->generateUUID($ref); + }, $expected_referrers); + + // Checks $expected_references. + $references = array_map(function ($ref) { + return $ref['id']; + }, isset($statement->refs) ? $statement->refs : []); + + // Checks $expected_referrers. + $referrers = (new \Statement) + ->select('statement.id') + ->where('statement.object.id', '=', $uuid) + ->where('statement.object.objectType', '=', 'StatementRef') + ->get()->toArray(); + $referrers = array_map(function ($ref) { + return $ref['statement']['id']; + }, $referrers); + + $this->assertEmpty(array_diff($expected_referrers, $referrers)); + } + + private function generateUUID($id) { + $len = strlen($id); + $start = str_repeat('0', 8 - $len); + return $id . $start . '-0000-0000-b000-000000000000'; + } + + public function testInsert1() { + $this->requestStatements([ + $this->createIdStatement('A', $this->createReferenceStatement('E')) + ]); + + $this->checkStatement('A', [], []); + } + + public function testInsert2() { + $this->requestStatements([ + $this->createIdStatement('A', $this->createReferenceStatement('E')) + ]); + + $this->requestStatements([ + $this->createIdStatement('C', $this->createReferenceStatement('A')), + $this->createIdStatement('D', $this->createReferenceStatement('B')) + ]); + + $this->checkStatement('A', [], ['C']); + $this->checkStatement('C', ['A'], []); + $this->checkStatement('D', [], []); + } + + public function testInsert3() { + $this->requestStatements([ + $this->createIdStatement('A', $this->createReferenceStatement('E')) + ]); + + $this->requestStatements([ + $this->createIdStatement('C', $this->createReferenceStatement('A')), + $this->createIdStatement('D', $this->createReferenceStatement('B')) + ]); + + $this->requestStatements([ + $this->createIdStatement('B', $this->createReferenceStatement('A')) + ]); + + $this->checkStatement('A', [], ['B', 'C']); + $this->checkStatement('B', ['A'], ['D']); + $this->checkStatement('C', ['A'], []); + $this->checkStatement('D', ['B', 'A'], []); + } + + public function testInsert4() { + $this->requestStatements([ + $this->createIdStatement('A', $this->createReferenceStatement('E')) + ]); + + $this->requestStatements([ + $this->createIdStatement('C', $this->createReferenceStatement('A')), + $this->createIdStatement('D', $this->createReferenceStatement('B')) + ]); + + $this->requestStatements([ + $this->createIdStatement('B', $this->createReferenceStatement('A')) + ]); + + $this->requestStatements([ + $this->createIdStatement('E', $this->createReferenceStatement('D')) + ]); + + $this->checkStatement('A', ['E', 'D', 'B'], ['B', 'C']); + $this->checkStatement('B', ['A', 'E', 'D'], ['D']); + $this->checkStatement('C', ['A', 'E', 'D', 'B'], []); + $this->checkStatement('D', ['B', 'A', 'E'], ['E']); + $this->checkStatement('E', ['D', 'B', 'A'], ['A']); + } + + public function tearDown() { + parent::tearDown(); + } + +} From 8d167287574907ffaf8fc4723361f4c1325970a3 Mon Sep 17 00:00:00 2001 From: Ryan Smith <0ryansmith1994@gmail.com> Date: Mon, 20 Apr 2015 14:01:46 +0100 Subject: [PATCH 26/34] Completes case change for directories. --- app/tests/{ffixtures => fixtures}/Repos/StatementFormatter1.json | 0 app/tests/{ffixtures => fixtures}/statement.json | 0 app/tests/{rrepos => repos}/Statement/EloquentIndexerTest.php | 0 app/tests/{rrepos => repos}/Statement/EloquentLinkerTest.php | 0 app/tests/{rrepos => repos}/Statement/EloquentShowerTest.php | 0 app/tests/{rrepos => repos}/Statement/EloquentTest.php | 0 app/tests/{rrepos => repos}/Statement/FormatterTest.php | 0 app/tests/{rrepos => repos}/Statement/IndexOptionsTest.php | 0 app/tests/{rrepos => repos}/Statement/OptionsTest.php | 0 app/tests/{rrepos => repos}/Statement/ShowOptionsTest.php | 0 app/tests/{rrepos => repos}/Statement/StoreOptionsTest.php | 0 11 files changed, 0 insertions(+), 0 deletions(-) rename app/tests/{ffixtures => fixtures}/Repos/StatementFormatter1.json (100%) rename app/tests/{ffixtures => fixtures}/statement.json (100%) rename app/tests/{rrepos => repos}/Statement/EloquentIndexerTest.php (100%) rename app/tests/{rrepos => repos}/Statement/EloquentLinkerTest.php (100%) rename app/tests/{rrepos => repos}/Statement/EloquentShowerTest.php (100%) rename app/tests/{rrepos => repos}/Statement/EloquentTest.php (100%) rename app/tests/{rrepos => repos}/Statement/FormatterTest.php (100%) rename app/tests/{rrepos => repos}/Statement/IndexOptionsTest.php (100%) rename app/tests/{rrepos => repos}/Statement/OptionsTest.php (100%) rename app/tests/{rrepos => repos}/Statement/ShowOptionsTest.php (100%) rename app/tests/{rrepos => repos}/Statement/StoreOptionsTest.php (100%) diff --git a/app/tests/ffixtures/Repos/StatementFormatter1.json b/app/tests/fixtures/Repos/StatementFormatter1.json similarity index 100% rename from app/tests/ffixtures/Repos/StatementFormatter1.json rename to app/tests/fixtures/Repos/StatementFormatter1.json diff --git a/app/tests/ffixtures/statement.json b/app/tests/fixtures/statement.json similarity index 100% rename from app/tests/ffixtures/statement.json rename to app/tests/fixtures/statement.json diff --git a/app/tests/rrepos/Statement/EloquentIndexerTest.php b/app/tests/repos/Statement/EloquentIndexerTest.php similarity index 100% rename from app/tests/rrepos/Statement/EloquentIndexerTest.php rename to app/tests/repos/Statement/EloquentIndexerTest.php diff --git a/app/tests/rrepos/Statement/EloquentLinkerTest.php b/app/tests/repos/Statement/EloquentLinkerTest.php similarity index 100% rename from app/tests/rrepos/Statement/EloquentLinkerTest.php rename to app/tests/repos/Statement/EloquentLinkerTest.php diff --git a/app/tests/rrepos/Statement/EloquentShowerTest.php b/app/tests/repos/Statement/EloquentShowerTest.php similarity index 100% rename from app/tests/rrepos/Statement/EloquentShowerTest.php rename to app/tests/repos/Statement/EloquentShowerTest.php diff --git a/app/tests/rrepos/Statement/EloquentTest.php b/app/tests/repos/Statement/EloquentTest.php similarity index 100% rename from app/tests/rrepos/Statement/EloquentTest.php rename to app/tests/repos/Statement/EloquentTest.php diff --git a/app/tests/rrepos/Statement/FormatterTest.php b/app/tests/repos/Statement/FormatterTest.php similarity index 100% rename from app/tests/rrepos/Statement/FormatterTest.php rename to app/tests/repos/Statement/FormatterTest.php diff --git a/app/tests/rrepos/Statement/IndexOptionsTest.php b/app/tests/repos/Statement/IndexOptionsTest.php similarity index 100% rename from app/tests/rrepos/Statement/IndexOptionsTest.php rename to app/tests/repos/Statement/IndexOptionsTest.php diff --git a/app/tests/rrepos/Statement/OptionsTest.php b/app/tests/repos/Statement/OptionsTest.php similarity index 100% rename from app/tests/rrepos/Statement/OptionsTest.php rename to app/tests/repos/Statement/OptionsTest.php diff --git a/app/tests/rrepos/Statement/ShowOptionsTest.php b/app/tests/repos/Statement/ShowOptionsTest.php similarity index 100% rename from app/tests/rrepos/Statement/ShowOptionsTest.php rename to app/tests/repos/Statement/ShowOptionsTest.php diff --git a/app/tests/rrepos/Statement/StoreOptionsTest.php b/app/tests/repos/Statement/StoreOptionsTest.php similarity index 100% rename from app/tests/rrepos/Statement/StoreOptionsTest.php rename to app/tests/repos/Statement/StoreOptionsTest.php From 31d04a8968a2a463347e2b00d8486601ad1fa1dd Mon Sep 17 00:00:00 2001 From: Ryan Smith <0ryansmith1994@gmail.com> Date: Mon, 20 Apr 2015 14:10:11 +0100 Subject: [PATCH 27/34] Only creates directory if attachments exist. --- app/locker/repository/Statement/FileAttacher.php | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/app/locker/repository/Statement/FileAttacher.php b/app/locker/repository/Statement/FileAttacher.php index a94d7a34c7..4206180690 100644 --- a/app/locker/repository/Statement/FileAttacher.php +++ b/app/locker/repository/Statement/FileAttacher.php @@ -13,7 +13,9 @@ class FileAttacher { */ public function store(array $attachments, StoreOptions $opts) { $dir = $this->getDir($opts); - if (!is_dir($dir)) mkdir($dir, null, true); + if (!is_dir($dir) && count($attachments > 0)) { + mkdir($dir, null, true); + } foreach ($attachments as $attachment) { $ext = $this->getExt($attachment->content_type); From efe40f88f34628da2aaf7c0896244146e2ae911f Mon Sep 17 00:00:00 2001 From: Ryan Smith <0ryansmith1994@gmail.com> Date: Tue, 21 Apr 2015 17:04:08 +0100 Subject: [PATCH 28/34] Fixes some conformance issues --- app/controllers/xapi/BaseController.php | 17 ++++----- app/controllers/xapi/StatementController.php | 7 ++-- .../xapi/StatementIndexController.php | 13 +++++-- .../xapi/StatementStoreController.php | 12 +++---- app/locker/helpers/Attachments.php | 34 +++++------------- .../repository/Statement/EloquentIndexer.php | 36 +++++++++---------- .../repository/Statement/EloquentInserter.php | 1 + .../repository/Statement/EloquentLinker.php | 15 ++++++-- .../repository/Statement/EloquentStorer.php | 14 +++++--- .../repository/Statement/EloquentVoider.php | 13 +++---- .../repository/Statement/FileAttacher.php | 7 +++- .../repository/Statement/IndexOptions.php | 2 +- app/locker/repository/Statement/Options.php | 14 ++++++-- app/routes.php | 2 +- 14 files changed, 103 insertions(+), 84 deletions(-) diff --git a/app/controllers/xapi/BaseController.php b/app/controllers/xapi/BaseController.php index b7e99ebe41..28bcea9ee6 100644 --- a/app/controllers/xapi/BaseController.php +++ b/app/controllers/xapi/BaseController.php @@ -38,17 +38,12 @@ public function setParameters(){ * @return mixed Result of the method. */ public function selectMethod() { - try { - switch ($this->method) { - case 'HEAD': - case 'GET': return $this->get(); - case 'PUT': return $this->update(); - case 'POST': return $this->store(); - case 'DELETE': return $this->destroy(); - } - } catch (\Exception $ex) { - $code = method_exists($ex, 'getStatusCode') ? $ex->getStatusCode() : 400; - throw new Exceptions\Exception($ex->getMessage(), $code, $ex); + switch ($this->method) { + case 'HEAD': + case 'GET': return $this->get(); + case 'PUT': return $this->update(); + case 'POST': return $this->store(); + case 'DELETE': return $this->destroy(); } } diff --git a/app/controllers/xapi/StatementController.php b/app/controllers/xapi/StatementController.php index 4dda17caf6..70b7bc9be7 100644 --- a/app/controllers/xapi/StatementController.php +++ b/app/controllers/xapi/StatementController.php @@ -3,6 +3,7 @@ use \Locker\Repository\Statement\Repository as Statement; use \Locker\Helpers\Attachments as Attachments; use \Locker\Helpers\Exceptions as Exceptions; +use \Locker\Helpers\Helpers as Helpers; class StatementController extends BaseController { @@ -23,7 +24,7 @@ class StatementController extends BaseController { */ public function __construct(Statement $statement) { parent::__construct(); - $this->statement = $statement; + $this->statements = $statement; $this->index_controller = new StatementIndexController($statement); $this->store_controller = new StatementStoreController($statement); } @@ -59,7 +60,7 @@ public function get() { public function update() { // Runs filters. if ($result = $this->checkVersion()) return $result; - $this->store_controller->update($this->lrs->_id); + return $this->store_controller->update($this->lrs->_id); } /** @@ -69,7 +70,7 @@ public function update() { public function store() { // Runs filters. if ($result = $this->checkVersion()) return $result; - $this->store_controller->store($this->lrs->_id); + return $this->store_controller->store($this->lrs->_id); } /** diff --git a/app/controllers/xapi/StatementIndexController.php b/app/controllers/xapi/StatementIndexController.php index fcf2ba86ad..57313ccb45 100644 --- a/app/controllers/xapi/StatementIndexController.php +++ b/app/controllers/xapi/StatementIndexController.php @@ -25,10 +25,17 @@ public function __construct(StatementRepo $statement_repo) { * @return Response */ public function index($lrs_id) { + // Gets the acceptable languages. + $langs = LockerRequest::header('Accept-Language', []); + $langs = is_array($langs) ? $langs : explode(',', $langs); + $langs = array_map(function ($lang) { + return explode(';', $lang)[0]; + }, $langs); + // Gets an index of the statements with the given options. list($statements, $count, $opts) = $this->statements->index(array_merge([ 'lrs_id' => $lrs_id, - 'langs' => LockerRequest::header('Accept-Language', []) + 'langs' => $langs ], LockerRequest::all())); // Defines the content type and body of the response. @@ -64,7 +71,7 @@ private function makeStatementsResult(array $statements, $count, array $opts) { // Creates the statement result. $statement_result = (object) [ - 'more' => $this->getMoreLink($count, $options['limit'], $options['offset']), + 'more' => $this->getMoreLink($count, $opts['limit'], $opts['offset']), 'statements' => $statements ]; @@ -101,7 +108,7 @@ private function makeAttachmentsResult(array $statements, $count, array $opts) { private function getMoreLink($count, $limit, $offset) { // Calculates the $next_offset. $next_offset = $offset + $limit; - if ($total <= $next_offset) return ''; + if ($count <= $next_offset) return ''; // Changes (when defined) or appends (when undefined) offset. $query = \Request::getQueryString(); diff --git a/app/controllers/xapi/StatementStoreController.php b/app/controllers/xapi/StatementStoreController.php index ceca7aadad..34f94a544b 100644 --- a/app/controllers/xapi/StatementStoreController.php +++ b/app/controllers/xapi/StatementStoreController.php @@ -59,8 +59,6 @@ private function getParts() { * @return Response */ public function store($lrs_id) { - Helpers::validateAtom(new XApiImt(LockerRequest::header('Content-Type'))); - if (LockerRequest::hasParam(StatementController::STATEMENT_ID)) { throw new Exceptions\Exception('Statement ID parameter is invalid.'); } @@ -73,9 +71,7 @@ public function store($lrs_id) { * @param String $lrs_id * @return Response */ - protected function update($lrs_id) { - Helpers::validateAtom(new XApiImt(LockerRequest::header('Content-Type'))); - + public function update($lrs_id) { $this->createStatements($lrs_id, function ($statements) { $statement_id = \LockerRequest::getParam(StatementController::STATEMENT_ID); @@ -99,14 +95,18 @@ protected function update($lrs_id) { * @return AssocArray Result of storing the statements. */ private function createStatements($lrs_id, Callable $modifier = null) { + Helpers::validateAtom(new XApiImt(LockerRequest::header('Content-Type'))); + // Gets parts of the request. $parts = $this->getParts(); $content = $parts['content']; // Decodes $statements from $content. $statements = json_decode($content); - if ($statements === null && $content != 'null' && $content != '') { + if ($statements === null && $content !== '') { throw new Exceptions\Exception('Invalid JSON'); + } else if ($statements === null) { + $statements = []; } // Ensures that $statements is an array. diff --git a/app/locker/helpers/Attachments.php b/app/locker/helpers/Attachments.php index 27c11dfdd4..4bea568611 100644 --- a/app/locker/helpers/Attachments.php +++ b/app/locker/helpers/Attachments.php @@ -1,5 +1,5 @@ getBoundary($content_type); + $boundary = static::getBoundary($content_type); // Fetch each part of the multipart document $parts = array_slice(explode($boundary, $content), 1); @@ -27,30 +27,21 @@ static function setAttachments($content_type, $content) { if ($part == "--") break; // Determines the delimiter. - $delim = strpos($part, "\r\n") ? "\r\n" : "\n"; + $delim = strpos($part, "\r\n") !== false ? "\r\n" : "\n"; // Separate body contents from headers $part = ltrim($part, $delim); list($raw_headers, $body) = explode($delim.$delim, $part, 2); - $headers = $this->getHeaders($raw_headers, $delim); + $headers = static::getHeaders($raw_headers, $delim); if ($count == 0) { - if ($headers['content-type'] !== 'application/json') { + if (!isset($headers['content-type']) || $headers['content-type'] !== 'application/json') { throw new Exceptions\Exception('Statements must make up the first part of the body.'); } - // Gets hash from each statement. - $statements = json_decode($body); - $statements = is_array($statements) ? $statements : [$statements]; - foreach ($statements as $statement){ - foreach($statement->attachments as $attachment){ - $sha_hashes[] = $attachment->sha2; - } - } - $return['body'] = $body; } else { - $this->validateHeaders($headers); + static::validateHeaders($headers, $sha_hashes); $return['attachments'][$count] = (object) [ 'hash' => $headers['x-experience-api-hash'], 'content_type' => $headers['content-type'], @@ -68,7 +59,7 @@ static function setAttachments($content_type, $content) { * @param String $delim * @return [String => Mixed] */ - private function getHeaders($raw_headers, $delim) { + private static function getHeaders($raw_headers, $delim) { $raw_headers = explode($delim, $raw_headers); $headers = []; foreach ($raw_headers as $header) { @@ -83,7 +74,7 @@ private function getHeaders($raw_headers, $delim) { * @param String $content_type * @return String */ - private function getBoundary($content_type) { + private static function getBoundary($content_type) { preg_match('/boundary=(.*)$/', $content_type, $matches); if (!isset($matches[1])) throw new Exceptions\Exception( 'You need to set a boundary if submitting attachments.' @@ -95,7 +86,7 @@ private function getBoundary($content_type) { * Validates the attachment headers. * @param [String => Mixed] $headers */ - private function validateHeaders(array $headers) { + private static function validateHeaders(array $headers, array $sha_hashes) { if (!isset($headers['content-type'])) { throw new Exceptions\Exception('You need to set a content type for your attachments.'); } @@ -110,13 +101,6 @@ private function validateHeaders(array $headers) { if (!isset($headers['x-experience-api-hash']) || $headers['x-experience-api-hash'] == '') { throw new Exceptions\Exception('Attachments require an api hash.'); } - - //check x-experience-api-hash is contained within a statement - if (!in_array($headers['x-experience-api-hash'], $sha_hashes)) { - throw new Exceptions\Exception( - 'Attachments need to contain x-experience-api-hash that is declared in statement.' - ); - } } } diff --git a/app/locker/repository/Statement/EloquentIndexer.php b/app/locker/repository/Statement/EloquentIndexer.php index 020236fd63..6efddd45d2 100644 --- a/app/locker/repository/Statement/EloquentIndexer.php +++ b/app/locker/repository/Statement/EloquentIndexer.php @@ -36,22 +36,22 @@ public function index(IndexOptions $opts) { return $this->matchActivity($value, $builder, $opts); }, 'verb' => function ($value, $builder, IndexOptions $opts) { - return $this->addWhere($builder, 'statement.verb.id', $value); + return $this->addWhere($builder, 'verb.id', $value); }, 'registration' => function ($value, $builder, IndexOptions $opts) { - return $this->addWhere($builder, 'statement.context.registration', $value); + return $this->addWhere($builder, 'context.registration', $value); }, 'since' => function ($value, $builder, IndexOptions $opts) { - return $this->addWhere($builder, 'statement.timestamp', $value); + return $this->addWhere($builder, 'timestamp', $value); }, 'until' => function ($value, $builder, IndexOptions $opts) { - return $this->addWhere($builder, 'statement.timestamp', $value); + return $this->addWhere($builder, 'timestamp', $value); }, 'active' => function ($value, $builder, IndexOptions $opts) { - return $this->addWhere($builder, 'active', $value); + return $builder->where('active', $value); }, 'voided' => function ($value, $builder, IndexOptions $opts) { - return $this->addWhere($builder, 'voided', $value); + return $builder->where('voided', $value); } ]); } @@ -66,7 +66,7 @@ public function index(IndexOptions $opts) { private function addWhere(Builder $builder, $key, $value) { return $builder->where(function ($query) use ($key, $value) { return $query - ->orWhere($key, $value) + ->orWhere('statement.'.$key, $value) ->orWhere('refs.'.$key, $value); }); } @@ -128,14 +128,14 @@ public function format(Builder $builder, IndexOptions $opts) { } // Returns the models. - return $builder + return json_decode($builder ->orderBy('statement.stored', $opts->getOpt('ascending')) ->skip($opts->getOpt('offset')) ->take($opts->getOpt('limit')) ->get() ->map(function (Model $model) use ($opts, $formatter) { return $formatter($this->formatModel($model), $opts); - }); + })); } /** @@ -149,13 +149,13 @@ private function matchAgent($agent, Builder $builder, IndexOptions $options) { $id_val = $agent->{$id_key}; return $builder->where(function ($query) use ($id_val, $opts) { - $keys = ["statement.actor.$id_key", "statement.object.$id_key"]; + $keys = ["actor.$id_key", "object.$id_key"]; if ($opts->getOpt('related_agents') === true) { $keys = array_merge($keys, [ - "statement.authority.$id_key", - "statement.context.instructor.$id_key", - "statement.context.team.$id_key" + "authority.$id_key", + "context.instructor.$id_key", + "context.team.$id_key" ]); } @@ -171,14 +171,14 @@ private function matchAgent($agent, Builder $builder, IndexOptions $options) { */ private function matchActivity($activity, Builder $builder, IndexOptions $options) { return $builder->where(function ($query) use ($activity, $opts) { - $keys = ['statement.object.id']; + $keys = ['object.id']; if ($opts->getOpt('related_activities') === true) { $keys = array_merge($keys, [ - 'statement.context.contextActivities.parent.id', - 'statement.context.contextActivities.grouping.id', - 'statement.context.contextActivities.category.id', - 'statement.context.contextActivities.other.id' + 'context.contextActivities.parent.id', + 'context.contextActivities.grouping.id', + 'context.contextActivities.category.id', + 'context.contextActivities.other.id' ]); } diff --git a/app/locker/repository/Statement/EloquentInserter.php b/app/locker/repository/Statement/EloquentInserter.php index 3edc30177d..69aa612e87 100644 --- a/app/locker/repository/Statement/EloquentInserter.php +++ b/app/locker/repository/Statement/EloquentInserter.php @@ -32,6 +32,7 @@ public function insert(array $statements, StoreOptions $opts) { private function checkForConflict(\stdClass $statement, StoreOptions $opts) { $duplicate = $this->where($opts) ->where('statement.id', $statement->id) + ->where('active', true) ->first(); if ($duplicate === null) return; diff --git a/app/locker/repository/Statement/EloquentLinker.php b/app/locker/repository/Statement/EloquentLinker.php index b53e06e7d8..b886b82b8a 100644 --- a/app/locker/repository/Statement/EloquentLinker.php +++ b/app/locker/repository/Statement/EloquentLinker.php @@ -18,6 +18,8 @@ class EloquentLinker extends EloquentReader implements LinkerInterface { * @param StoreOptions $opts */ public function updateReferences(array $statements, StoreOptions $opts) { + $this->voider = strpos(json_encode($statements), 'voided') !== false; + if ($this->voider) \Log::info('updateReferences'); $this->downed = new Collection(); $this->to_update = array_map(function (\stdClass $statement) use ($opts) { return $this->getModel($statement->id, $opts); @@ -33,7 +35,8 @@ public function updateReferences(array $statements, StoreOptions $opts) { * @param \stdClass $statement * @return Boolean */ - private function isReferencing(\stdClass $statement) { + protected function isReferencing(\stdClass $statement) { + if ($this->voider) \Log::info('isReferencing'); return ( isset($statement->object->objectType) && $statement->object->objectType === 'StatementRef' @@ -46,7 +49,8 @@ private function isReferencing(\stdClass $statement) { * @param StoreOptions $opts * @return [Model] */ - private function getModel($statement_id, StoreOptions $opts) { + protected function getModel($statement_id, StoreOptions $opts) { + if ($this->voider) \Log::info('getModel'); $model = $this->where($opts) ->where('statement.id', $statement_id) ->first(); @@ -62,6 +66,7 @@ private function getModel($statement_id, StoreOptions $opts) { * @return [Model] */ private function upLink(Model $model, array $visited, StoreOptions $opts) { + if ($this->voider) \Log::info('upLink'); $statement = $this->formatModel($model); if (in_array($statement->id, $visited)) return []; $visited[] = $statement->id; @@ -85,6 +90,7 @@ private function upLink(Model $model, array $visited, StoreOptions $opts) { * @return [Model] */ private function downLink(Model $model, array $visited, StoreOptions $opts) { + if ($this->voider) \Log::info('downLink'); $statement = $this->formatModel($model); if (in_array($model, $visited)) { return array_slice($visited, array_search($model, $visited)); @@ -109,6 +115,7 @@ private function downLink(Model $model, array $visited, StoreOptions $opts) { * @return [\stdClass] */ private function upRefs(\stdClass $statement, StoreOptions $opts) { + if ($this->voider) \Log::info('upRefs'); return $this->where($opts) ->where('statement.object.id', $statement->id) ->where('statement.object.objectType', 'StatementRef') @@ -122,6 +129,7 @@ private function upRefs(\stdClass $statement, StoreOptions $opts) { * @return Model */ private function downRef(\stdClass $statement, StoreOptions $opts) { + if ($this->voider) \Log::info('downRef'); if (!$this->isReferencing($statement)) return null; return $this->getModel($statement->object->id, $opts); } @@ -133,11 +141,11 @@ private function downRef(\stdClass $statement, StoreOptions $opts) { * @param StoreOptions $opts */ private function setRefs(\stdClass $statement, array $refs, StoreOptions $opts) { + if ($this->voider) \Log::info('setRefs'); $this->where($opts) ->where('statement.id', $statement->id) ->update([ 'refs' => array_map(function ($ref) { - if (get_class($ref) === 'stdClass') \Log::info(json_encode($ref)); return $ref->statement; }, $refs) ]); @@ -148,6 +156,7 @@ private function setRefs(\stdClass $statement, array $refs, StoreOptions $opts) * @param Model $model */ private function unQueue(Model $model) { + if ($this->voider) \Log::info('unQueue'); $updated_index = array_search($model, $this->to_update); if ($updated_index !== false) { array_splice($this->to_update, $updated_index, 1); diff --git a/app/locker/repository/Statement/EloquentStorer.php b/app/locker/repository/Statement/EloquentStorer.php index 82569e82c6..e025a1af30 100644 --- a/app/locker/repository/Statement/EloquentStorer.php +++ b/app/locker/repository/Statement/EloquentStorer.php @@ -9,7 +9,7 @@ public function store(array $statements, array $attachments, StoreOptions $opts) class EloquentStorer extends EloquentReader implements Storer { - protected $inserter, $linker, $voider, $attacher; + protected $inserter, $linker, $voider, $attacher, $hashes; public function __construct() { $this->inserter = new EloquentInserter(); @@ -27,15 +27,14 @@ public function __construct() { */ public function store(array $statements, array $attachments, StoreOptions $opts) { $id_statements = $this->constructValidStatements($statements, $opts); - $ids = array_keys($statements); + $ids = array_keys($id_statements); $statements = array_values($id_statements); $this->inserter->insert($statements, $opts); $this->linker->updateReferences($statements, $opts); $this->voider->voidStatements($statements, $opts); - + $this->attacher->store($attachments, $this->hashes, $opts); $this->activateStatements($ids, $opts); - $this->attacher->store($attachments, $opts); return $ids; } @@ -48,6 +47,7 @@ public function store(array $statements, array $attachments, StoreOptions $opts) */ private function constructValidStatements(array $statements, StoreOptions $opts) { $constructed = []; + $this->hashes = []; foreach ($statements as $statement) { $statement->authority = $opts->getOpt('authority'); @@ -66,6 +66,12 @@ private function constructValidStatements(array $statements, StoreOptions $opts) Helpers::validateAtom($constructed_statement, 'statement'); $statement = $constructed_statement->getValue(); + // Gets attachment hashes. + $attachments = !isset($statement->attachments) ? [] : $statement->attachments; + foreach ($attachments as $attachment) { + $this->hashes[] = $attachment->sha2; + } + // Adds $statement to $constructed. if (isset($constructed[$statement->id])) { $this->inserter->compareForConflict($statement, $constructed[$statement->id]); diff --git a/app/locker/repository/Statement/EloquentVoider.php b/app/locker/repository/Statement/EloquentVoider.php index e3ce8b3b09..3f08b11fd5 100644 --- a/app/locker/repository/Statement/EloquentVoider.php +++ b/app/locker/repository/Statement/EloquentVoider.php @@ -1,5 +1,6 @@ getModel($voider->object->id, $opts); if ($voided !== null) { - if ($this->isVoidinging($this->formatModel($voided))) throw new \Exception(trans( + if ($this->isVoiding($this->formatModel($voided))) throw new Exceptions\Exception(trans( 'xapi.errors.void_voider' )); $voided->voided = true; $voided->save(); } else { - throw new \Exception(trans( + throw new Exceptions\Exception(trans( 'xapi.errors.void_null' )); } @@ -45,13 +46,13 @@ private function voidStatement(\stdClass $voider, StoreOptions $opts) { /** * Determines if a statement is a voiding statement. - * @param \stdClass $voider + * @param \stdClass $statement * @return Boolean */ - private function isVoiding(\stdClass $voider) { + private function isVoiding(\stdClass $statement) { return ( - isset($voider->object->id) && - $voider->object->id === 'http://adlnet.gov/expapi/verbs/voided' && + isset($statement->verb->id) && + $statement->verb->id === 'http://adlnet.gov/expapi/verbs/voided' && $this->isReferencing($statement) ); } diff --git a/app/locker/repository/Statement/FileAttacher.php b/app/locker/repository/Statement/FileAttacher.php index 4206180690..4a3aadb3b3 100644 --- a/app/locker/repository/Statement/FileAttacher.php +++ b/app/locker/repository/Statement/FileAttacher.php @@ -9,15 +9,20 @@ class FileAttacher { /** * Stores attachments with the given options. * @param [\stdClass] $attachments + * @param [String] $hashes * @param StoreOptions $opts */ - public function store(array $attachments, StoreOptions $opts) { + public function store(array $attachments, array $hashes, StoreOptions $opts) { $dir = $this->getDir($opts); if (!is_dir($dir) && count($attachments > 0)) { mkdir($dir, null, true); } foreach ($attachments as $attachment) { + if (!in_array($attachment->hash, $hashes)) throw new Exceptions\Exception( + 'Attachment hash does not exist in given statements' + ); + $ext = $this->getExt($attachment->content_type); if ($ext === false) throw new Exceptions\Exception( 'This file type cannot be supported' diff --git a/app/locker/repository/Statement/IndexOptions.php b/app/locker/repository/Statement/IndexOptions.php index c403e43c3f..6d030ecce1 100644 --- a/app/locker/repository/Statement/IndexOptions.php +++ b/app/locker/repository/Statement/IndexOptions.php @@ -37,7 +37,7 @@ class IndexOptions extends Options { 'format' => 'String', 'offset' => 'Integer', 'limit' => 'Integer', - 'langs' => 'Collection', + 'langs' => ['Language'], 'attachments' => 'Boolean', 'lrs_id' => 'String' ]; diff --git a/app/locker/repository/Statement/Options.php b/app/locker/repository/Statement/Options.php index 37863e3fd0..547caff34b 100644 --- a/app/locker/repository/Statement/Options.php +++ b/app/locker/repository/Statement/Options.php @@ -30,8 +30,18 @@ public function getOpt($opt) { protected function validate($opts) { foreach ($opts as $key => $value) { if ($value !== null) { - $class = '\Locker\XApi\\'.$this->types[$key]; - Helpers::validateAtom(new $class($value)); + if (is_array($this->types[$key])) { + $class = '\Locker\XApi\\'.$this->types[$key][0]; + if (!is_array($value)) { + throw new Exceptions\Exception("$key must be an array."); + } + foreach ($value as $item) { + Helpers::validateAtom(new $class($item)); + } + } else { + $class = '\Locker\XApi\\'.$this->types[$key]; + Helpers::validateAtom(new $class($value)); + } } } diff --git a/app/routes.php b/app/routes.php index a1c19050e9..a7e5500e9f 100644 --- a/app/routes.php +++ b/app/routes.php @@ -514,7 +514,7 @@ }); App::error(function(Exception $exception) { - Log::error($exception); + //Log::error($exception); $code = method_exists($exception, 'getStatusCode') ? $exception->getStatusCode() : 500; if (Request::segment(1) == "data" || Request::segment(1) == "api") { From 2183bfea757b1cac1fb18107a381f85ac73887f0 Mon Sep 17 00:00:00 2001 From: Ryan Smith <0ryansmith1994@gmail.com> Date: Tue, 21 Apr 2015 17:26:51 +0100 Subject: [PATCH 29/34] Adds debug logs. --- app/locker/repository/Statement/EloquentLinker.php | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) diff --git a/app/locker/repository/Statement/EloquentLinker.php b/app/locker/repository/Statement/EloquentLinker.php index b886b82b8a..9adf822f6b 100644 --- a/app/locker/repository/Statement/EloquentLinker.php +++ b/app/locker/repository/Statement/EloquentLinker.php @@ -19,7 +19,6 @@ class EloquentLinker extends EloquentReader implements LinkerInterface { */ public function updateReferences(array $statements, StoreOptions $opts) { $this->voider = strpos(json_encode($statements), 'voided') !== false; - if ($this->voider) \Log::info('updateReferences'); $this->downed = new Collection(); $this->to_update = array_map(function (\stdClass $statement) use ($opts) { return $this->getModel($statement->id, $opts); @@ -36,7 +35,6 @@ public function updateReferences(array $statements, StoreOptions $opts) { * @return Boolean */ protected function isReferencing(\stdClass $statement) { - if ($this->voider) \Log::info('isReferencing'); return ( isset($statement->object->objectType) && $statement->object->objectType === 'StatementRef' @@ -50,7 +48,6 @@ protected function isReferencing(\stdClass $statement) { * @return [Model] */ protected function getModel($statement_id, StoreOptions $opts) { - if ($this->voider) \Log::info('getModel'); $model = $this->where($opts) ->where('statement.id', $statement_id) ->first(); @@ -66,7 +63,6 @@ protected function getModel($statement_id, StoreOptions $opts) { * @return [Model] */ private function upLink(Model $model, array $visited, StoreOptions $opts) { - if ($this->voider) \Log::info('upLink'); $statement = $this->formatModel($model); if (in_array($statement->id, $visited)) return []; $visited[] = $statement->id; @@ -90,7 +86,6 @@ private function upLink(Model $model, array $visited, StoreOptions $opts) { * @return [Model] */ private function downLink(Model $model, array $visited, StoreOptions $opts) { - if ($this->voider) \Log::info('downLink'); $statement = $this->formatModel($model); if (in_array($model, $visited)) { return array_slice($visited, array_search($model, $visited)); @@ -115,7 +110,6 @@ private function downLink(Model $model, array $visited, StoreOptions $opts) { * @return [\stdClass] */ private function upRefs(\stdClass $statement, StoreOptions $opts) { - if ($this->voider) \Log::info('upRefs'); return $this->where($opts) ->where('statement.object.id', $statement->id) ->where('statement.object.objectType', 'StatementRef') @@ -129,7 +123,6 @@ private function upRefs(\stdClass $statement, StoreOptions $opts) { * @return Model */ private function downRef(\stdClass $statement, StoreOptions $opts) { - if ($this->voider) \Log::info('downRef'); if (!$this->isReferencing($statement)) return null; return $this->getModel($statement->object->id, $opts); } @@ -141,7 +134,10 @@ private function downRef(\stdClass $statement, StoreOptions $opts) { * @param StoreOptions $opts */ private function setRefs(\stdClass $statement, array $refs, StoreOptions $opts) { - if ($this->voider) \Log::info('setRefs'); + if ($this->voider) \Log::info('$statement', [$statement]); + if ($this->voider) \Log::info('$refs', [$refs]); + if ($this->voider) \Log::info('$opts', [$opts]); + if ($this->voider) \Log::info('start setRefs'); $this->where($opts) ->where('statement.id', $statement->id) ->update([ @@ -149,6 +145,7 @@ private function setRefs(\stdClass $statement, array $refs, StoreOptions $opts) return $ref->statement; }, $refs) ]); + if ($this->voider) \Log::info('end setRefs'); } /** @@ -156,7 +153,6 @@ private function setRefs(\stdClass $statement, array $refs, StoreOptions $opts) * @param Model $model */ private function unQueue(Model $model) { - if ($this->voider) \Log::info('unQueue'); $updated_index = array_search($model, $this->to_update); if ($updated_index !== false) { array_splice($this->to_update, $updated_index, 1); From 2cba601587017e9036559d00e20319d768d6b5ff Mon Sep 17 00:00:00 2001 From: Ryan Smith <0ryansmith1994@gmail.com> Date: Wed, 22 Apr 2015 11:38:45 +0100 Subject: [PATCH 30/34] Fixes tests. --- .../xapi/StatementIndexController.php | 11 +++++- app/locker/helpers/Attachments.php | 2 +- .../repository/Statement/EloquentIndexer.php | 38 ++++++++++--------- .../repository/Statement/EloquentInserter.php | 5 ++- .../repository/Statement/EloquentLinker.php | 10 ++--- .../repository/Statement/EloquentReader.php | 3 +- .../repository/Statement/FileAttacher.php | 2 +- .../repository/Statement/IndexOptions.php | 2 +- app/routes.php | 2 +- .../repos/Statement/EloquentIndexerTest.php | 9 ++--- 10 files changed, 47 insertions(+), 37 deletions(-) diff --git a/app/controllers/xapi/StatementIndexController.php b/app/controllers/xapi/StatementIndexController.php index 57313ccb45..4d3f8c0439 100644 --- a/app/controllers/xapi/StatementIndexController.php +++ b/app/controllers/xapi/StatementIndexController.php @@ -32,11 +32,20 @@ public function index($lrs_id) { return explode(';', $lang)[0]; }, $langs); + // Gets the params. + $params = LockerRequest::all(); + if (isset($params['agent'])) { + $decoded_agent = json_decode($params['agent']); + if ($decoded_agent !== null) { + $params['agent'] = $decoded_agent; + } + } + // Gets an index of the statements with the given options. list($statements, $count, $opts) = $this->statements->index(array_merge([ 'lrs_id' => $lrs_id, 'langs' => $langs - ], LockerRequest::all())); + ], $params)); // Defines the content type and body of the response. if ($opts['attachments'] === true) { diff --git a/app/locker/helpers/Attachments.php b/app/locker/helpers/Attachments.php index 4bea568611..394ab6e841 100644 --- a/app/locker/helpers/Attachments.php +++ b/app/locker/helpers/Attachments.php @@ -14,7 +14,7 @@ class Attachments { * **/ static function setAttachments($content_type, $content) { - $return = []; + $return = ['body' => '', 'attachments' => []]; $sha_hashes = []; $boundary = static::getBoundary($content_type); diff --git a/app/locker/repository/Statement/EloquentIndexer.php b/app/locker/repository/Statement/EloquentIndexer.php index 6efddd45d2..a6a3f4a222 100644 --- a/app/locker/repository/Statement/EloquentIndexer.php +++ b/app/locker/repository/Statement/EloquentIndexer.php @@ -42,10 +42,10 @@ public function index(IndexOptions $opts) { return $this->addWhere($builder, 'context.registration', $value); }, 'since' => function ($value, $builder, IndexOptions $opts) { - return $this->addWhere($builder, 'timestamp', $value); + return $this->addWhere($builder, 'stored', $value, '>'); }, 'until' => function ($value, $builder, IndexOptions $opts) { - return $this->addWhere($builder, 'timestamp', $value); + return $this->addWhere($builder, 'stored', $value, '<='); }, 'active' => function ($value, $builder, IndexOptions $opts) { return $builder->where('active', $value); @@ -63,11 +63,11 @@ public function index(IndexOptions $opts) { * @param Mixed $value * @return Builder */ - private function addWhere(Builder $builder, $key, $value) { - return $builder->where(function ($query) use ($key, $value) { + private function addWhere(Builder $builder, $key, $value, $op = '=') { + return $builder->where(function ($query) use ($key, $value, $op) { return $query - ->orWhere('statement.'.$key, $value) - ->orWhere('refs.'.$key, $value); + ->orWhere('statement.'.$key, $op, $value) + ->orWhere('refs.'.$key, $op, $value); }); } @@ -81,7 +81,11 @@ private function addWhere(Builder $builder, $key, $value) { private function addWheres(Builder $builder, array $keys, $value) { return $builder->where(function ($query) use ($keys, $value) { foreach ($keys as $key) { - $query = $this->addWhere($query, $key, $value); + $query->orWhere(function ($query) use ($key, $value) { + return $query + ->orWhere('statement.'.$key, $value) + ->orWhere('refs.'.$key, $value); + }); } return $query; }); @@ -129,7 +133,7 @@ public function format(Builder $builder, IndexOptions $opts) { // Returns the models. return json_decode($builder - ->orderBy('statement.stored', $opts->getOpt('ascending')) + ->orderBy('statement.stored', $opts->getOpt('ascending') ? 'ASC' : 'DESC') ->skip($opts->getOpt('offset')) ->take($opts->getOpt('limit')) ->get() @@ -141,15 +145,15 @@ public function format(Builder $builder, IndexOptions $opts) { /** * Constructs a Mongo match using the given agent and options. * @param String $agent Agent to be matched. - * @param IndexOptions $options Index options. + * @param IndexOptions $opts Index options. * @return Builder */ - private function matchAgent($agent, Builder $builder, IndexOptions $options) { + private function matchAgent($agent, Builder $builder, IndexOptions $opts) { $id_key = Helpers::getAgentIdentifier($agent); $id_val = $agent->{$id_key}; - return $builder->where(function ($query) use ($id_val, $opts) { - $keys = ["actor.$id_key", "object.$id_key"]; + return $builder->where(function ($query) use ($id_key, $id_val, $builder, $opts) { + $keys = ["actor.$id_key", "actor.members.$id_key", "object.$id_key"]; if ($opts->getOpt('related_agents') === true) { $keys = array_merge($keys, [ @@ -159,18 +163,18 @@ private function matchAgent($agent, Builder $builder, IndexOptions $options) { ]); } - $query = $this->addWheres($keys); + $query = $this->addWheres($builder, $keys, $id_val); }); } /** * Constructs a Mongo match using the given activity and options. * @param String $activity Activity to be matched. - * @param IndexOptions $options Index options. + * @param IndexOptions $opts Index options. * @return Builder */ - private function matchActivity($activity, Builder $builder, IndexOptions $options) { - return $builder->where(function ($query) use ($activity, $opts) { + private function matchActivity($activity, Builder $builder, IndexOptions $opts) { + return $builder->where(function ($query) use ($activity, $builder, $opts) { $keys = ['object.id']; if ($opts->getOpt('related_activities') === true) { @@ -182,7 +186,7 @@ private function matchActivity($activity, Builder $builder, IndexOptions $option ]); } - $query = $this->addWheres($keys); + $query = $this->addWheres($builder, $keys, $activity); }); } diff --git a/app/locker/repository/Statement/EloquentInserter.php b/app/locker/repository/Statement/EloquentInserter.php index 69aa612e87..bd4084bc06 100644 --- a/app/locker/repository/Statement/EloquentInserter.php +++ b/app/locker/repository/Statement/EloquentInserter.php @@ -1,13 +1,14 @@ ['_id' => $opts->getOpt('lrs_id')], - 'statement' => $statement, + 'statement' => Helpers::replaceFullStop(json_decode(json_encode($statement), true)), 'active' => false, 'voided' => false, 'timestamp' => new \MongoDate(strtotime($statement->timestamp)) diff --git a/app/locker/repository/Statement/EloquentLinker.php b/app/locker/repository/Statement/EloquentLinker.php index 9adf822f6b..29efe3c866 100644 --- a/app/locker/repository/Statement/EloquentLinker.php +++ b/app/locker/repository/Statement/EloquentLinker.php @@ -2,6 +2,7 @@ use \Illuminate\Database\Eloquent\Model as Model; use \Illuminate\Database\Eloquent\Collection as Collection; +use \Locker\Helpers\Helpers as Helpers; interface LinkerInterface { public function updateReferences(array $statements, StoreOptions $opts); @@ -51,7 +52,7 @@ protected function getModel($statement_id, StoreOptions $opts) { $model = $this->where($opts) ->where('statement.id', $statement_id) ->first(); - + return $model; } @@ -134,18 +135,13 @@ private function downRef(\stdClass $statement, StoreOptions $opts) { * @param StoreOptions $opts */ private function setRefs(\stdClass $statement, array $refs, StoreOptions $opts) { - if ($this->voider) \Log::info('$statement', [$statement]); - if ($this->voider) \Log::info('$refs', [$refs]); - if ($this->voider) \Log::info('$opts', [$opts]); - if ($this->voider) \Log::info('start setRefs'); $this->where($opts) ->where('statement.id', $statement->id) ->update([ 'refs' => array_map(function ($ref) { - return $ref->statement; + return Helpers::replaceFullStop(json_decode(json_encode($ref->statement), true)); }, $refs) ]); - if ($this->voider) \Log::info('end setRefs'); } /** diff --git a/app/locker/repository/Statement/EloquentReader.php b/app/locker/repository/Statement/EloquentReader.php index 48b17cf7ad..5d9742043f 100644 --- a/app/locker/repository/Statement/EloquentReader.php +++ b/app/locker/repository/Statement/EloquentReader.php @@ -1,6 +1,7 @@ statement)); + return Helpers::replaceHTMLEntity($model->statement); } } diff --git a/app/locker/repository/Statement/FileAttacher.php b/app/locker/repository/Statement/FileAttacher.php index 4a3aadb3b3..3e8edb15b0 100644 --- a/app/locker/repository/Statement/FileAttacher.php +++ b/app/locker/repository/Statement/FileAttacher.php @@ -14,7 +14,7 @@ class FileAttacher { */ public function store(array $attachments, array $hashes, StoreOptions $opts) { $dir = $this->getDir($opts); - if (!is_dir($dir) && count($attachments > 0)) { + if (!is_dir($dir) && count($attachments > 0) && !empty($attachments)) { mkdir($dir, null, true); } diff --git a/app/locker/repository/Statement/IndexOptions.php b/app/locker/repository/Statement/IndexOptions.php index 6d030ecce1..c9ca3e8311 100644 --- a/app/locker/repository/Statement/IndexOptions.php +++ b/app/locker/repository/Statement/IndexOptions.php @@ -23,7 +23,7 @@ class IndexOptions extends Options { 'attachments' => false ]; protected $types = [ - 'agent' => 'Agent', + 'agent' => 'Actor', 'activity' => 'IRI', 'verb' => 'IRI', 'registration' => 'UUID', diff --git a/app/routes.php b/app/routes.php index a7e5500e9f..a1c19050e9 100644 --- a/app/routes.php +++ b/app/routes.php @@ -514,7 +514,7 @@ }); App::error(function(Exception $exception) { - //Log::error($exception); + Log::error($exception); $code = method_exists($exception, 'getStatusCode') ? $exception->getStatusCode() : 500; if (Request::segment(1) == "data" || Request::segment(1) == "api") { diff --git a/app/tests/repos/Statement/EloquentIndexerTest.php b/app/tests/repos/Statement/EloquentIndexerTest.php index 5622c407d0..3ae0fb5ed5 100644 --- a/app/tests/repos/Statement/EloquentIndexerTest.php +++ b/app/tests/repos/Statement/EloquentIndexerTest.php @@ -27,16 +27,15 @@ public function testFormat() { $result = $this->indexer->index($opts); $result = $this->indexer->format($result, $opts); - $this->assertEquals(true, is_object($result)); - $this->assertEquals('Illuminate\Database\Eloquent\Collection', get_class($result)); - $this->assertEquals(count($this->statements), $result->count()); - $result->each(function ($statement) { + $this->assertEquals(true, is_array($result)); + $this->assertEquals(count($this->statements), count($result)); + foreach ($result as $statement) { $this->assertEquals(true, is_object($statement)); $this->assertEquals(true, isset($statement->id)); $this->assertEquals(true, is_string($statement->id)); $expected_statement = $this->statements[0]->statement; $this->assertStatementMatch($expected_statement, $statement); - }); + } } public function testCount() { From 93b288ec5771abd0c37302b5e9b22d92b97a70a0 Mon Sep 17 00:00:00 2001 From: Ryan Smith <0ryansmith1994@gmail.com> Date: Wed, 22 Apr 2015 14:42:10 +0100 Subject: [PATCH 31/34] Fixes views. --- app/controllers/LrsController.php | 23 +++++++++++-------- app/controllers/SiteController.php | 10 ++++---- .../Statement/EloquentRepository.php | 14 ++++++++++- 3 files changed, 32 insertions(+), 15 deletions(-) diff --git a/app/controllers/LrsController.php b/app/controllers/LrsController.php index 9bc9f3e8f6..9281f93318 100644 --- a/app/controllers/LrsController.php +++ b/app/controllers/LrsController.php @@ -2,6 +2,8 @@ use \Locker\Repository\Lrs\Repository as LrsRepo; use \Locker\Repository\Statement\Repository as StatementRepo; +use \Locker\Repository\Statement\EloquentIndexer as StatementIndexer; +use \Locker\Repository\Statement\IndexOptions as IndexOptions; class LrsController extends BaseController { @@ -66,7 +68,7 @@ public function store() { //lrs input validation $rules['title'] = 'required'; - $rules['description'] = ''; + $rules['description'] = ''; $validator = \Validator::make($data, $rules); if ($validator->fails()) return \Redirect::back()->withErrors($validator); @@ -103,7 +105,7 @@ public function update($lrs_id){ $data = \Input::all(); //lrs input validation - $rules['title'] = 'required'; + $rules['title'] = 'required'; $validator = \Validator::make($data, $rules); if ($validator->fails()) { return \Redirect::back()->withErrors($validator); @@ -123,7 +125,7 @@ public function update($lrs_id){ /** * Display the specified resource. - * This is a temp hack until the single page app for + * This is a temp hack until the single page app for * analytics is ready. v1.0 stable. * @param String $lrs_id * @return View @@ -187,10 +189,13 @@ public function destroy($lrs_id){ * @return View */ public function statements($lrs_id){ - $statements = $this->statement->index($lrs_id, [], [ + $statements = (new StatementIndexer)->index(new IndexOptions([ + 'lrs_id' => $lrs_id, 'ascending' => false, - 'limit' => $this->statement->count($lrs_id) - ])->paginate(15); + 'limit' => $this->statement->count([ + 'lrs_id' => $lrs_id + ]) + ]))->paginate(15); return View::make('partials.statements.list', array_merge($this->getLrs($lrs_id), [ 'statements' => $statements, @@ -240,7 +245,7 @@ public function editCredentials( $lrs_id ){ $message_type = 'error'; $message = trans('update_key_error'); } - + return Redirect::back()->with($message_type, $message); } @@ -251,7 +256,7 @@ public function editCredentials( $lrs_id ){ */ public function users($lrs_id) { $opts = $this->getLrs($lrs_id); - return View::make('partials.users.list', array_merge($opts, [ + return View::make('partials.users.list', array_merge($opts, [ 'users' => $opts['lrs']->users, 'user_nav' => true ])); @@ -260,7 +265,7 @@ public function users($lrs_id) { public function inviteUsersForm($lrs_id) { $opts = $this->getLrs($lrs_id); - return View::make('partials.lrs.invite', array_merge($opts, [ + return View::make('partials.lrs.invite', array_merge($opts, [ 'users' => $opts['lrs']->users, 'user_nav' => true ])); diff --git a/app/controllers/SiteController.php b/app/controllers/SiteController.php index 0c53c9a1b3..3d74c06e51 100644 --- a/app/controllers/SiteController.php +++ b/app/controllers/SiteController.php @@ -20,7 +20,7 @@ public function __construct(SiteRepo $site, LrsRepo $lrs, UserRepo $user, Statem $this->beforeFilter('auth'); $this->beforeFilter('auth.super', array('except' => array('inviteUsers'))); - $this->beforeFilter('csrf', array('only' => array('update', 'verifyUser', 'inviteUsers'))); + $this->beforeFilter('csrf', array('only' => array('update', 'verifyUser', 'inviteUsers'))); } /** @@ -35,7 +35,7 @@ public function index(){ $admin_dashboard = new \app\locker\data\dashboards\AdminDashboard(); return View::make('partials.site.dashboard', [ - 'site' => $site, + 'site' => $site, 'list' => $list, 'stats' => $admin_dashboard->getFullStats(), 'graph_data' => $admin_dashboard->getGraphData() @@ -51,7 +51,7 @@ public function index(){ public function edit($id){ $site = $this->site->find($id); return View::make('partials.site.edit', [ - 'site' => $site, + 'site' => $site, 'settings_nav' => true ]); } @@ -122,7 +122,7 @@ public function lrs(){ $lrss = $this->lrs->index($opts); return Response::json(array_map(function ($lrs) { - $lrs->statement_total = $this->statement->count($lrs->_id); + $lrs->statement_total = $this->statement->count(['lrs_id' => $lrs->_id]); return $lrs; }, $lrss)); } @@ -148,7 +148,7 @@ public function users() { */ public function inviteUsersForm() { return View::make('partials.site.invite', [ - 'users_nav' => true, + 'users_nav' => true, 'admin_dash' => true ]); } diff --git a/app/locker/repository/Statement/EloquentRepository.php b/app/locker/repository/Statement/EloquentRepository.php index effbeac225..39868a4f17 100644 --- a/app/locker/repository/Statement/EloquentRepository.php +++ b/app/locker/repository/Statement/EloquentRepository.php @@ -5,6 +5,7 @@ public function store(array $statements, array $attachments, array $opts); public function index(array $opts); public function show($id, array $opts); public function getAttachments(array $statements, array $opts); + public function count(array $opts); } class EloquentRepository implements Repository { @@ -64,4 +65,15 @@ public function show($id, array $opts) { public function getAttachments(array $statements, array $opts) { return $this->attacher->index($statements, new IndexOptions($opts)); } -} \ No newline at end of file + + /** + * Gets a count of all the statements available with the given options. + * @param [String => Mixed] $opts + * @return Int + */ + public function count(array $opts) { + $opts = new IndexOptions($opts); + $builder = $this->indexer->index($opts); + return $this->indexer->count($builder, $opts); + } +} From 7a10662b852cd9afca4f359650d59160323cc811 Mon Sep 17 00:00:00 2001 From: Ryan Smith <0ryansmith1994@gmail.com> Date: Wed, 22 Apr 2015 14:56:18 +0100 Subject: [PATCH 32/34] Adds readme to tests directory. --- app/tests/readme.md | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) create mode 100644 app/tests/readme.md diff --git a/app/tests/readme.md b/app/tests/readme.md new file mode 100644 index 0000000000..b511630e12 --- /dev/null +++ b/app/tests/readme.md @@ -0,0 +1,25 @@ +# Tests +This file explains the purposes of some of the classes and directories within this directory. + +## Abstract Classes +### TestCase +This is a class to extend for all tests. + +### InstanceTestCase (extends TestCase) +This is a class to extend for testing a Learning Locker (LL) instance/installation. Any tests that depend on a LL instance being setup before testing should utilise this class. + +### LrsTestCase (extends InstanceTestCase) +This is a class to extend for testing an LRS inside a LL instance. Any tests that depend on an LRS being created before testing should utilise this class. + +### StatementsTestCase (extends LrsTestCase) +This is a class to extend for testing retrieval of statements inside an LRS. Any tests that depend on a statements being created before or during testing should utilise this class. + +## Directories +### fixtures +This directory should contain any files required by the tests. + +### repos +This directory should contain tests for repositories (data in, data out). + +### routes +This directory should contain tests for routes (request in, response out). Inside this directory there is a RouteTestTrait that should be used by all classes inside this directory for sending requests. From ea9ed90be2414a91c8edb14c415ec4ceb63c221c Mon Sep 17 00:00:00 2001 From: Ryan Smith <0ryansmith1994@gmail.com> Date: Wed, 22 Apr 2015 15:00:35 +0100 Subject: [PATCH 33/34] Updates broken Travis test. --- app/tests/routes/StatementRefTest.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/app/tests/routes/StatementRefTest.php b/app/tests/routes/StatementRefTest.php index 6fc18e984c..e1c0f5742b 100644 --- a/app/tests/routes/StatementRefTest.php +++ b/app/tests/routes/StatementRefTest.php @@ -61,7 +61,8 @@ private function checkStatement($id, $expected_references = [], $expected_referr return $ref['statement']['id']; }, $referrers); - $this->assertEmpty(array_diff($expected_referrers, $referrers)); + $diff = array_diff($expected_referrers, $referrers); + $this->assertEquals(true, empty($diff) || count($diff) === 0, json_encode($diff)); } private function generateUUID($id) { From 5d1d452f4a773ea051474135eb38070b3f4a07af Mon Sep 17 00:00:00 2001 From: Ryan Smith <0ryansmith1994@gmail.com> Date: Wed, 22 Apr 2015 15:51:37 +0100 Subject: [PATCH 34/34] Improves tests. --- app/tests/StatementsTestCase.php | 8 ++++---- app/tests/routes/StatementRefTest.php | 6 +++++- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/app/tests/StatementsTestCase.php b/app/tests/StatementsTestCase.php index 1eabbc5061..d74093901f 100644 --- a/app/tests/StatementsTestCase.php +++ b/app/tests/StatementsTestCase.php @@ -14,7 +14,7 @@ public function setUp() { protected function generateStatement($statement = []) { $timestamp = Helpers::getCurrentDate(); - return [ + return array_merge([ 'actor' => [ 'mbox' => 'mailto:test@example.com', 'objectType' => 'Agent' @@ -32,17 +32,17 @@ protected function generateStatement($statement = []) { 'mbox' => 'mailto:test@example.com', 'objectType' => 'Agent' ] - ]; + ], $statement); } protected function createStatement(\Lrs $lrs, array $statement) { - $model = new \Statement(array_merge([ + $model = new \Statement([ 'lrs' => ['_id' => $lrs->_id], 'statement' => $statement, 'active' => true, 'voided' => false, 'refs' => [] - ], $statement)); + ]); $model->timestamp = new \MongoDate(strtotime($model->statement['timestamp'])); $model->save(); return $model; diff --git a/app/tests/routes/StatementRefTest.php b/app/tests/routes/StatementRefTest.php index e1c0f5742b..19cfe381a6 100644 --- a/app/tests/routes/StatementRefTest.php +++ b/app/tests/routes/StatementRefTest.php @@ -62,7 +62,11 @@ private function checkStatement($id, $expected_references = [], $expected_referr }, $referrers); $diff = array_diff($expected_referrers, $referrers); - $this->assertEquals(true, empty($diff) || count($diff) === 0, json_encode($diff)); + $this->assertEquals(true, empty($diff) || count($diff) === 0, + json_encode($diff). + json_encode($expected_referrers). + json_encode($referrers) + ); } private function generateUUID($id) {