added logic to the skill manifest endpoint to reconcile the manifest uploaded by the device with the manifest stored on the database
parent
a85f8d7723
commit
93a212e673
|
@ -107,7 +107,7 @@ def validate_response(context):
|
|||
response_data['membership'], has_item('id')
|
||||
)
|
||||
|
||||
assert_that(len(response_data['agreements']), equal_to(2))
|
||||
assert_that(len(response_data['agreements']), equal_to(3))
|
||||
agreement = response_data['agreements'][0]
|
||||
assert_that(agreement['type'], equal_to(PRIVACY_POLICY))
|
||||
assert_that(
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import json
|
||||
from datetime import datetime
|
||||
from http import HTTPStatus
|
||||
from logging import getLogger
|
||||
|
||||
|
@ -8,33 +9,82 @@ from schematics.types import (
|
|||
StringType,
|
||||
ModelType,
|
||||
ListType,
|
||||
DateTimeType,
|
||||
IntType,
|
||||
BooleanType
|
||||
BooleanType,
|
||||
TimestampType
|
||||
)
|
||||
|
||||
from selene.api import PublicEndpoint
|
||||
from selene.data.device import DeviceSkillRepository
|
||||
from selene.data.device import ManifestSkill, DeviceSkillRepository
|
||||
from selene.data.skill import SkillRepository
|
||||
|
||||
|
||||
class SkillManifest(Model):
|
||||
class SkillManifestReconciler(object):
|
||||
def __init__(self, db, skill_manifest, device_skills):
|
||||
self.db = db
|
||||
self.skill_manifest_repo = DeviceSkillRepository(db)
|
||||
self.skill_repo = SkillRepository(self.db)
|
||||
self.device_manifest = {sm.skill_gid: sm for sm in skill_manifest}
|
||||
self.db_manifest = {ds.skill_gid: ds for ds in device_skills}
|
||||
self.device_manifest_global_ids = {
|
||||
gid for gid in self.device_manifest.keys()
|
||||
}
|
||||
self.db_manifest_global_ids = {gid for gid in self.db_manifest}
|
||||
|
||||
def reconcile(self):
|
||||
self._update_skills()
|
||||
self._remove_skills()
|
||||
self._add_skills()
|
||||
|
||||
def _update_skills(self):
|
||||
common_global_ids = self.device_manifest_global_ids.intersection(
|
||||
self.db_manifest_global_ids
|
||||
)
|
||||
for gid in common_global_ids:
|
||||
if self.device_manifest[gid] == self.db_manifest[gid]:
|
||||
self.skill_manifest_repo.update_manifest_skill(
|
||||
self.device_manifest[gid]
|
||||
)
|
||||
|
||||
def _remove_skills(self):
|
||||
skills_to_remove = self.db_manifest_global_ids.difference(
|
||||
self.device_manifest_global_ids
|
||||
)
|
||||
for gid in skills_to_remove:
|
||||
manifest_skill = self.db_manifest[gid]
|
||||
self.skill_manifest_repo.remove_manifest_skill(manifest_skill)
|
||||
if manifest_skill.device_id in gid:
|
||||
self.skill_repo.remove_by_gid(gid)
|
||||
|
||||
def _add_skills(self):
|
||||
skills_to_add = self.device_manifest_global_ids.difference(
|
||||
self.db_manifest_global_ids
|
||||
)
|
||||
|
||||
for gid in skills_to_add:
|
||||
skill_id = self.skill_repo.ensure_skill_exists(gid)
|
||||
self.skill_manifest_repo.add_manifest_skill(
|
||||
skill_id,
|
||||
self.device_manifest[gid]
|
||||
)
|
||||
|
||||
|
||||
class RequestManifestSkill(Model):
|
||||
name = StringType(required=True)
|
||||
origin = StringType(default='')
|
||||
installation = StringType(default='')
|
||||
origin = StringType(required=True)
|
||||
installation = StringType(required=True)
|
||||
failure_message = StringType(default='')
|
||||
status = StringType(default='')
|
||||
beta = BooleanType(default='')
|
||||
installed = DateTimeType()
|
||||
updated = DateTimeType()
|
||||
update = DateTimeType()
|
||||
skill_gid = StringType()
|
||||
status = StringType(required=True)
|
||||
beta = BooleanType(required=True)
|
||||
installed = TimestampType(required=True)
|
||||
updated = TimestampType(required=True)
|
||||
skill_gid = StringType(required=True)
|
||||
|
||||
|
||||
class SkillJson(Model):
|
||||
class SkillManifestRequest(Model):
|
||||
blacklist = ListType(StringType)
|
||||
version = IntType()
|
||||
skills = ListType(ModelType(SkillManifest, required=True))
|
||||
skills = ListType(ModelType(RequestManifestSkill, required=True))
|
||||
|
||||
|
||||
_log = getLogger(__package__)
|
||||
|
@ -69,9 +119,12 @@ class DeviceSkillManifestEndpoint(PublicEndpoint):
|
|||
return response
|
||||
|
||||
def _build_skill_manifest(self, device_id):
|
||||
device_skills = self.device_skill_repo.get_skills_for_device(device_id)
|
||||
skills_manifest = []
|
||||
for skill in device_skills:
|
||||
"""Convert the DeviceSkill dataclass into the format for the response"""
|
||||
skill_manifest = self.device_skill_repo.get_skill_manifest_for_device(
|
||||
device_id
|
||||
)
|
||||
response_skills = []
|
||||
for skill in skill_manifest:
|
||||
response_skill = dict(
|
||||
origin=skill.install_method,
|
||||
installation=skill.install_status,
|
||||
|
@ -85,15 +138,50 @@ class DeviceSkillManifestEndpoint(PublicEndpoint):
|
|||
if skill.update_ts is not None:
|
||||
response_skill['updated'] = skill.update_ts.timestamp()
|
||||
|
||||
skills_manifest.append(response_skill)
|
||||
skills_manifest = {'skills': skills_manifest}
|
||||
response_skills.append(response_skill)
|
||||
|
||||
return json.dumps(skills_manifest)
|
||||
return json.dumps({'skills': response_skills})
|
||||
|
||||
def put(self, device_id):
|
||||
self._authenticate(device_id)
|
||||
payload = json.loads(self.request.data)
|
||||
skill_json = SkillJson(payload)
|
||||
skill_json.validate()
|
||||
SkillRepository(self.db).update_skills_manifest(device_id, payload['skills'])
|
||||
self._validate_put_request()
|
||||
self._update_skill_manifest(device_id)
|
||||
return '', HTTPStatus.OK
|
||||
|
||||
def _validate_put_request(self):
|
||||
request_data = SkillManifestRequest(self.request.json)
|
||||
request_data.validate()
|
||||
|
||||
def _update_skill_manifest(self, device_id):
|
||||
db_skill_manifest = self.device_skill_repo.get_skill_manifest_for_device(
|
||||
device_id
|
||||
)
|
||||
device_skill_manifest = []
|
||||
for manifest_skill in self.request.json['skills']:
|
||||
self._convert_manifest_timestamps(manifest_skill)
|
||||
device_skill_manifest.append(
|
||||
ManifestSkill(
|
||||
device_id=device_id,
|
||||
install_method=manifest_skill['origin'],
|
||||
install_status=manifest_skill['installation'],
|
||||
install_failure_reason=manifest_skill.get('failure_message'),
|
||||
install_ts=manifest_skill['installed'],
|
||||
skill_gid=manifest_skill['skill_gid'],
|
||||
update_ts=manifest_skill['updated']
|
||||
)
|
||||
)
|
||||
reconciler = SkillManifestReconciler(
|
||||
self.db,
|
||||
device_skill_manifest,
|
||||
db_skill_manifest
|
||||
)
|
||||
reconciler.reconcile()
|
||||
|
||||
@staticmethod
|
||||
def _convert_manifest_timestamps(manifest_skill):
|
||||
for key in ('installed', 'updated'):
|
||||
value = manifest_skill[key]
|
||||
if value:
|
||||
manifest_skill[key] = datetime.fromtimestamp(value)
|
||||
else:
|
||||
manifest_skill[key] = None
|
||||
|
|
|
@ -1,8 +1,47 @@
|
|||
Feature: Upload and fetch skills manifest
|
||||
Feature: Device can upload and fetch skills manifest
|
||||
|
||||
Scenario: A skill manifest is successfully uploaded
|
||||
Given a device with a skill
|
||||
When a skill manifest is uploaded
|
||||
Then the skill manifest endpoint should return 200 status code
|
||||
And the skill manifest should be added
|
||||
Scenario: Device retrieves its manifest from the API
|
||||
Given an authorized device
|
||||
When a device requests its skill manifest
|
||||
Then the request will be successful
|
||||
And the response will contain the manifest
|
||||
|
||||
Scenario: Device uploads an unchanged manifest
|
||||
Given an authorized device
|
||||
When a device uploads a skill manifest without changes
|
||||
Then the request will be successful
|
||||
And the skill manifest on the database is unchanged
|
||||
And device last contact timestamp is updated
|
||||
|
||||
Scenario: Device uploads a manifest with an updated skill
|
||||
Given an authorized device
|
||||
When a device uploads a skill manifest with an updated skill
|
||||
Then the request will be successful
|
||||
And the skill manifest on the database is unchanged
|
||||
And device last contact timestamp is updated
|
||||
|
||||
Scenario: Device uploads a manifest with a deleted skill
|
||||
Given an authorized device
|
||||
When a device uploads a skill manifest with a deleted skill
|
||||
Then the request will be successful
|
||||
And the skill is removed from the manifest on the database
|
||||
And device last contact timestamp is updated
|
||||
|
||||
@device_specific_skill
|
||||
Scenario: Device uploads a manifest with a deleted device-specific skill
|
||||
Given an authorized device
|
||||
When a device uploads a skill manifest with a deleted device-specific skill
|
||||
Then the request will be successful
|
||||
And the device-specific skill is removed from the manifest on the database
|
||||
And the device-specific skill is removed from the database
|
||||
And device last contact timestamp is updated
|
||||
|
||||
@new_skill
|
||||
Scenario: Device uploads a manifest with a new skill
|
||||
Given an authorized device
|
||||
When a device uploads a skill manifest with a new skill
|
||||
Then the request will be successful
|
||||
And the skill is added to the database
|
||||
And the skill is added to the manifest on the database
|
||||
And device last contact timestamp is updated
|
||||
|
||||
|
|
|
@ -3,12 +3,16 @@ from behave import fixture, use_fixture
|
|||
from public_api.api import public
|
||||
from selene.api import generate_device_login
|
||||
from selene.api.etag import ETagManager
|
||||
from selene.data.device import PreferenceRepository
|
||||
from selene.testing.account import add_account, remove_account
|
||||
from selene.testing.agreement import add_agreements, remove_agreements
|
||||
from selene.testing.account_geography import add_account_geography
|
||||
from selene.testing.account_preference import add_account_preference
|
||||
from selene.testing.device import add_device
|
||||
from selene.testing.device_skill import (
|
||||
add_skill_to_manifest,
|
||||
remove_manifest_skill
|
||||
)
|
||||
from selene.testing.skill import add_skill, remove_skill
|
||||
from selene.testing.text_to_speech import (
|
||||
add_text_to_speech,
|
||||
remove_text_to_speech
|
||||
|
@ -55,7 +59,16 @@ def before_scenario(context, _):
|
|||
context.wake_word = add_wake_word(context.db)
|
||||
context.voice = add_text_to_speech(context.db)
|
||||
_add_device(context)
|
||||
except Exception:
|
||||
context.skill = add_skill(
|
||||
context.db,
|
||||
skill_global_id='selene-test-skill|19.02'
|
||||
)
|
||||
context.manifest_skill = add_skill_to_manifest(
|
||||
context.db,
|
||||
context.device_id,
|
||||
context.skill
|
||||
)
|
||||
except:
|
||||
import traceback
|
||||
print(traceback.print_exc())
|
||||
|
||||
|
@ -64,6 +77,7 @@ def after_scenario(context, _):
|
|||
remove_account(context.db, context.account)
|
||||
remove_wake_word(context.db, context.wake_word)
|
||||
remove_text_to_speech(context.db, context.voice)
|
||||
remove_skill(context.db, skill_global_id=context.skill.skill_gid)
|
||||
|
||||
|
||||
def _add_device(context):
|
||||
|
@ -71,3 +85,29 @@ def _add_device(context):
|
|||
context.device_id = device_id
|
||||
context.device_name = 'Selene Test Device'
|
||||
context.device_login = generate_device_login(device_id, context.cache)
|
||||
context.access_token = context.device_login['accessToken']
|
||||
|
||||
|
||||
def before_tag(context, tag):
|
||||
if tag == 'device_specific_skill':
|
||||
_add_device_specific_skil(context)
|
||||
|
||||
|
||||
def _add_device_specific_skil(context):
|
||||
context.device_specific_skill = add_skill(
|
||||
context.db,
|
||||
skill_global_id='@{device_id}|device-specific-skill|19.02'.format(
|
||||
device_id=context.device_id
|
||||
)
|
||||
)
|
||||
context.device_specific_manifest = add_skill_to_manifest(
|
||||
context.db,
|
||||
context.device_id,
|
||||
context.skill
|
||||
)
|
||||
|
||||
|
||||
def after_tag(context, tag):
|
||||
if tag == 'new_skill':
|
||||
remove_manifest_skill(context.db, context.new_manifest_skill)
|
||||
remove_skill(context.db, context.new_skill.skill_gid)
|
||||
|
|
|
@ -1,95 +1,217 @@
|
|||
import json
|
||||
from datetime import datetime
|
||||
from http import HTTPStatus
|
||||
|
||||
from behave import given, when, then
|
||||
from hamcrest import assert_that, equal_to
|
||||
from behave import when, then
|
||||
from hamcrest import (
|
||||
assert_that,
|
||||
equal_to,
|
||||
has_entry,
|
||||
has_key,
|
||||
is_in,
|
||||
is_,
|
||||
none,
|
||||
not_none,
|
||||
not_
|
||||
)
|
||||
|
||||
skill_manifest = {
|
||||
"blacklist": [],
|
||||
"version": 1,
|
||||
"skills": [
|
||||
{
|
||||
"name": "fallback-wolfram-alpha",
|
||||
"origin": "default",
|
||||
"beta": False,
|
||||
"status": "active",
|
||||
"installed": datetime.now().timestamp(),
|
||||
"updated": datetime.now().timestamp(),
|
||||
"installation": "installed",
|
||||
"update": 0,
|
||||
"skill_gid": "fallback-wolfram-alpha|19.02",
|
||||
}
|
||||
]
|
||||
}
|
||||
from selene.data.device import DeviceSkillRepository, ManifestSkill
|
||||
from selene.data.skill import SkillRepository, Skill
|
||||
|
||||
|
||||
skill = {
|
||||
'skill_gid': 'fallback-wolfram-alpha|19.02',
|
||||
'skillMetadata': {
|
||||
'sections': [
|
||||
{
|
||||
'name': 'section1',
|
||||
'fields': [
|
||||
{
|
||||
'label': 'test'
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
def _build_manifest_upload(manifest_skills):
|
||||
upload_skills = []
|
||||
for skill in manifest_skills:
|
||||
upload_skills.append(
|
||||
dict(
|
||||
name="test-skill-name",
|
||||
origin=skill.install_method,
|
||||
beta=False,
|
||||
status="active",
|
||||
installed=skill.install_ts.timestamp(),
|
||||
updated=skill.update_ts.timestamp(),
|
||||
installation=skill.install_status,
|
||||
skill_gid=skill.skill_gid,
|
||||
)
|
||||
)
|
||||
return {
|
||||
"blacklist": [],
|
||||
"version": 1,
|
||||
"skills": upload_skills
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@given('a device with a skill')
|
||||
def device_skill(context):
|
||||
login = context.device_login
|
||||
device_id = login['uuid']
|
||||
access_token = login['accessToken']
|
||||
headers = dict(Authorization='Bearer {token}'.format(token=access_token))
|
||||
context.client.put(
|
||||
'/v1/device/{uuid}/skill'.format(uuid=device_id),
|
||||
data=json.dumps(skill),
|
||||
content_type='application_json',
|
||||
headers=headers
|
||||
)
|
||||
|
||||
|
||||
@when('a skill manifest is uploaded')
|
||||
def device_skill_manifest(context):
|
||||
login = context.device_login
|
||||
device_id = login['uuid']
|
||||
access_token = login['accessToken']
|
||||
headers = dict(Authorization='Bearer {token}'.format(token=access_token))
|
||||
context.upload_skill_manifest_response = context.client.put(
|
||||
'/v1/device/{uuid}/skillJson'.format(uuid=device_id),
|
||||
data=json.dumps(skill_manifest),
|
||||
content_type='application_json',
|
||||
headers=headers
|
||||
)
|
||||
|
||||
|
||||
@then('the skill manifest endpoint should return 200 status code')
|
||||
def validate_skill_manifest_upload(context):
|
||||
response = context.upload_skill_manifest_response
|
||||
assert_that(response.status_code, equal_to(HTTPStatus.OK))
|
||||
|
||||
|
||||
@then('the skill manifest should be added')
|
||||
@when('a device requests its skill manifest')
|
||||
def get_skill_manifest(context):
|
||||
login = context.device_login
|
||||
device_id = login['uuid']
|
||||
access_token = login['accessToken']
|
||||
headers = dict(Authorization='Bearer {token}'.format(token=access_token))
|
||||
response = context.client.get(
|
||||
'/v1/device/{uuid}/skillJson'.format(uuid=device_id),
|
||||
headers=headers
|
||||
context.response = context.client.get(
|
||||
'/v1/device/{device_id}/skillJson'.format(device_id=context.device_id),
|
||||
content_type='application/json',
|
||||
headers=context.request_header
|
||||
)
|
||||
assert_that(response.status_code, equal_to(HTTPStatus.OK))
|
||||
skill_manifest_from_db = json.loads(response.data)
|
||||
skill = skill_manifest_from_db['skills'][0]
|
||||
expected_skill = skill_manifest['skills'][0]
|
||||
assert_that(skill['origin'], equal_to(expected_skill['origin']))
|
||||
assert_that(skill['installation'], equal_to(expected_skill['installation']))
|
||||
assert_that(skill['installed'], equal_to(expected_skill['installed']))
|
||||
assert_that(skill['updated'], equal_to(expected_skill['updated']))
|
||||
|
||||
|
||||
@when('a device uploads a skill manifest without changes')
|
||||
def upload_unchanged_skill_manifest(context):
|
||||
skill_manifest = _build_manifest_upload([context.manifest_skill])
|
||||
_upload_skill_manifest(context, skill_manifest)
|
||||
|
||||
|
||||
@when('a device uploads a skill manifest with an updated skill')
|
||||
def upload_unchanged_skill_manifest(context):
|
||||
skill_manifest = _build_manifest_upload([context.manifest_skill])
|
||||
context.update_ts = datetime.utcnow().timestamp()
|
||||
skill_manifest['skills'][0]['updated'] = context.update_ts
|
||||
_upload_skill_manifest(context, skill_manifest)
|
||||
|
||||
|
||||
@when('a device uploads a skill manifest with a deleted skill')
|
||||
def upload_unchanged_skill_manifest(context):
|
||||
skill_manifest = _build_manifest_upload([])
|
||||
_upload_skill_manifest(context, skill_manifest)
|
||||
|
||||
|
||||
@when('a device uploads a skill manifest with a deleted device-specific skill')
|
||||
def upload_skill_manifest_no_device_specific(context):
|
||||
skill_manifest = _build_manifest_upload([context.manifest_skill])
|
||||
_upload_skill_manifest(context, skill_manifest)
|
||||
|
||||
|
||||
@when('a device uploads a skill manifest with a new skill')
|
||||
def upload_unchanged_skill_manifest(context):
|
||||
context.new_skill = Skill(skill_gid='new-test-skill|19.02')
|
||||
context.new_manifest_skill = ManifestSkill(
|
||||
device_id=context.device_id,
|
||||
install_method='test_install_method',
|
||||
install_status='test_install_status',
|
||||
skill_gid=context.new_skill.skill_gid,
|
||||
install_ts=datetime.utcnow(),
|
||||
update_ts=datetime.utcnow()
|
||||
)
|
||||
|
||||
skill_manifest = _build_manifest_upload(
|
||||
[context.manifest_skill, context.new_manifest_skill]
|
||||
)
|
||||
_upload_skill_manifest(context, skill_manifest)
|
||||
|
||||
|
||||
def _upload_skill_manifest(context, skill_manifest):
|
||||
context.response = context.client.put(
|
||||
'/v1/device/{device_id}/skillJson'.format(device_id=context.device_id),
|
||||
data=json.dumps(skill_manifest),
|
||||
content_type='application/json',
|
||||
headers=context.request_header
|
||||
)
|
||||
|
||||
|
||||
@then('the response will contain the manifest')
|
||||
def check_skill_manifest_response(context):
|
||||
response = context.response.json
|
||||
assert_that(response, has_key('skills'))
|
||||
assert_that(len(response['skills']), equal_to(1))
|
||||
manifest_skill = response['skills'][0]
|
||||
assert_that(
|
||||
manifest_skill,
|
||||
has_entry('origin', context.manifest_skill.install_method)
|
||||
)
|
||||
assert_that(
|
||||
manifest_skill,
|
||||
has_entry('installation', context.manifest_skill.install_status))
|
||||
assert_that(
|
||||
manifest_skill,
|
||||
has_entry(
|
||||
'failure_message',
|
||||
context.manifest_skill.install_failure_reason
|
||||
)
|
||||
)
|
||||
assert_that(
|
||||
manifest_skill,
|
||||
has_entry('installed', context.manifest_skill.install_ts.timestamp())
|
||||
)
|
||||
assert_that(
|
||||
manifest_skill,
|
||||
has_entry('updated', context.manifest_skill.update_ts.timestamp())
|
||||
)
|
||||
assert_that(
|
||||
manifest_skill,
|
||||
has_entry('skill_gid', context.manifest_skill.skill_gid)
|
||||
)
|
||||
|
||||
|
||||
@then('the skill manifest on the database is unchanged')
|
||||
def get_unchanged_skill_manifest(context):
|
||||
device_skill_repo = DeviceSkillRepository(context.db)
|
||||
skill_manifest = device_skill_repo.get_skill_manifest_for_device(
|
||||
context.device_id
|
||||
)
|
||||
assert_that(len(skill_manifest), equal_to(1))
|
||||
manifest_skill = skill_manifest[0]
|
||||
assert_that(manifest_skill, equal_to(context.manifest_skill))
|
||||
|
||||
|
||||
@then('the skill manifest on the database is updated')
|
||||
def get_updated_skill_manifest(context):
|
||||
device_skill_repo = DeviceSkillRepository(context.db)
|
||||
skill_manifest = device_skill_repo.get_skill_manifest_for_device(
|
||||
context.device_id
|
||||
)
|
||||
assert_that(len(skill_manifest), equal_to(1))
|
||||
manifest_skill = skill_manifest[0]
|
||||
assert_that(manifest_skill, not_(equal_to(context.manifest_skill)))
|
||||
manifest_skill.update_ts = context.update_ts
|
||||
assert_that(manifest_skill, (equal_to(context.manifest_skill)))
|
||||
|
||||
|
||||
@then('the skill is removed from the manifest on the database')
|
||||
def get_empty_skill_manifest(context):
|
||||
device_skill_repo = DeviceSkillRepository(context.db)
|
||||
skill_manifest = device_skill_repo.get_skill_manifest_for_device(
|
||||
context.device_id
|
||||
)
|
||||
assert_that(len(skill_manifest), equal_to(0))
|
||||
|
||||
|
||||
@then('the device-specific skill is removed from the manifest on the database')
|
||||
def get_skill_manifest_no_device_specific(context):
|
||||
device_skill_repo = DeviceSkillRepository(context.db)
|
||||
skill_manifest = device_skill_repo.get_skill_manifest_for_device(
|
||||
context.device_id
|
||||
)
|
||||
assert_that(len(skill_manifest), equal_to(1))
|
||||
remaining_skill = skill_manifest[0]
|
||||
assert_that(
|
||||
remaining_skill.skill_gid,
|
||||
not_(equal_to(context.device_specific_skill.skill_gid))
|
||||
)
|
||||
|
||||
|
||||
@then('the device-specific skill is removed from the database')
|
||||
def ensure_device_specific_skill_removed(context):
|
||||
skill_repo = SkillRepository(context.db)
|
||||
skill = skill_repo.get_skill_by_global_id(
|
||||
context.device_specific_skill.skill_gid
|
||||
)
|
||||
assert_that(skill, is_(none()))
|
||||
|
||||
|
||||
@then('the skill is added to the manifest on the database')
|
||||
def get_skill_manifest_new_skill(context):
|
||||
device_skill_repo = DeviceSkillRepository(context.db)
|
||||
skill_manifest = device_skill_repo.get_skill_manifest_for_device(
|
||||
context.device_id
|
||||
)
|
||||
assert_that(len(skill_manifest), equal_to(2))
|
||||
assert_that(context.manifest_skill, is_in(skill_manifest))
|
||||
|
||||
# the device_skill id is not part of the request data so clear it out
|
||||
for manifest_skill in skill_manifest:
|
||||
if manifest_skill.skill_gid == context.new_skill.skill_gid:
|
||||
manifest_skill.id = None
|
||||
assert_that(context.new_manifest_skill, is_in(skill_manifest))
|
||||
|
||||
|
||||
@then('the skill is added to the database')
|
||||
def get_new_skill(context):
|
||||
skill_repo = SkillRepository(context.db)
|
||||
skill = skill_repo.get_skill_by_global_id(
|
||||
context.new_skill.skill_gid
|
||||
)
|
||||
assert_that(skill, is_(not_none()))
|
||||
|
|
|
@ -10,6 +10,6 @@ OPEN_DATASET = 'Open Dataset'
|
|||
class Agreement(object):
|
||||
type: str
|
||||
version: str
|
||||
content: str
|
||||
effective_date: date
|
||||
id: str = None
|
||||
content: str = None
|
||||
|
|
|
@ -5,11 +5,26 @@ from datetime import datetime
|
|||
@dataclass
|
||||
class DeviceSkill(object):
|
||||
id: str
|
||||
device_name: str
|
||||
device_id: str
|
||||
install_method: str
|
||||
install_status: str
|
||||
skill_id: str
|
||||
skill_gid: str
|
||||
device_name: str = None
|
||||
install_failure_reason: str = None
|
||||
install_ts: datetime = None
|
||||
update_ts: datetime = None
|
||||
skill_settings: dict = None
|
||||
skill_settings_display_id: str = None
|
||||
|
||||
|
||||
@dataclass
|
||||
class ManifestSkill(object):
|
||||
device_id: str
|
||||
install_method: str
|
||||
install_status: str
|
||||
skill_gid: str
|
||||
install_failure_reason: str = None
|
||||
install_ts: datetime = None
|
||||
update_ts: datetime = None
|
||||
id: str = None
|
||||
|
|
|
@ -106,7 +106,7 @@ class DeviceRepository(RepositoryBase):
|
|||
)
|
||||
self.cursor.update(db_request)
|
||||
|
||||
def add_wake_word(self, account_id: str, wake_word: WakeWord) -> str:
|
||||
def add_wake_word(self, wake_word: WakeWord, account_id: str = None) -> str:
|
||||
"""Adds a row to the wake word table
|
||||
|
||||
:param account_id: the account that we are linking to the wake word
|
||||
|
|
|
@ -1,5 +1,8 @@
|
|||
"""Data repository code for the skills on a device"""
|
||||
from ..entity.device_skill import DeviceSkill
|
||||
from dataclasses import asdict
|
||||
from typing import List
|
||||
|
||||
from ..entity.device_skill import DeviceSkill, ManifestSkill
|
||||
from ...repository_base import RepositoryBase
|
||||
|
||||
|
||||
|
@ -7,20 +10,15 @@ class DeviceSkillRepository(RepositoryBase):
|
|||
def __init__(self, db):
|
||||
super(DeviceSkillRepository, self).__init__(db, __file__)
|
||||
|
||||
def get_installed_skills_for_account(self, account_id: str):
|
||||
def get_installed_skills_for_account(
|
||||
self, account_id: str
|
||||
) -> List[DeviceSkill]:
|
||||
return self._select_all_into_dataclass(
|
||||
dataclass=DeviceSkill,
|
||||
sql_file_name='get_device_skills_for_account.sql',
|
||||
args=dict(account_id=account_id)
|
||||
)
|
||||
|
||||
def get_skills_for_device(self, device_id: str):
|
||||
return self._select_all_into_dataclass(
|
||||
dataclass=DeviceSkill,
|
||||
sql_file_name='get_skills_for_device.sql',
|
||||
args=dict(device_id=device_id)
|
||||
)
|
||||
|
||||
def update_skill_settings(
|
||||
self, account_id: str, device_names: tuple, skill_name: str
|
||||
):
|
||||
|
@ -33,3 +31,39 @@ class DeviceSkillRepository(RepositoryBase):
|
|||
)
|
||||
)
|
||||
self.cursor.update(db_request)
|
||||
|
||||
def get_skill_manifest_for_device(self, device_id: str):
|
||||
return self._select_all_into_dataclass(
|
||||
dataclass=ManifestSkill,
|
||||
sql_file_name='get_device_skill_manifest.sql',
|
||||
args=dict(device_id=device_id)
|
||||
)
|
||||
|
||||
def update_manifest_skill(self, manifest_skill: ManifestSkill):
|
||||
db_request = self._build_db_request(
|
||||
sql_file_name='update_skill_manifest.sql',
|
||||
args=asdict(manifest_skill)
|
||||
)
|
||||
|
||||
self.cursor.update(db_request)
|
||||
|
||||
def add_manifest_skill(self, skill_id: str, manifest_skill: ManifestSkill):
|
||||
db_request_args = dict(skill_id=skill_id)
|
||||
db_request_args.update(asdict(manifest_skill))
|
||||
db_request = self._build_db_request(
|
||||
sql_file_name='add_manifest_skill.sql',
|
||||
args=db_request_args
|
||||
)
|
||||
db_result = self.cursor.insert_returning(db_request)
|
||||
|
||||
return db_result['id']
|
||||
|
||||
def remove_manifest_skill(self, manifest_skill: ManifestSkill):
|
||||
db_request = self._build_db_request(
|
||||
sql_file_name='remove_manifest_skill.sql',
|
||||
args=dict(
|
||||
device_id=manifest_skill.device_id,
|
||||
skill_gid=manifest_skill.skill_gid
|
||||
)
|
||||
)
|
||||
self.cursor.delete(db_request)
|
||||
|
|
|
@ -0,0 +1,22 @@
|
|||
INSERT INTO
|
||||
device.device_skill (
|
||||
device_id,
|
||||
skill_id,
|
||||
install_method,
|
||||
install_status,
|
||||
install_failure_reason,
|
||||
install_ts,
|
||||
update_ts
|
||||
)
|
||||
VALUES
|
||||
(
|
||||
%(device_id)s,
|
||||
%(skill_id)s,
|
||||
%(install_method)s,
|
||||
%(install_status)s,
|
||||
%(install_failure_reason)s,
|
||||
%(install_ts)s,
|
||||
%(update_ts)s
|
||||
)
|
||||
RETURNING
|
||||
id
|
|
@ -1,11 +1,10 @@
|
|||
SELECT
|
||||
d.name AS device_name,
|
||||
ds.id,
|
||||
ds.device_id,
|
||||
ds.install_failure_reason,
|
||||
ds.install_method,
|
||||
ds.install_status,
|
||||
ds.install_ts,
|
||||
ds.skill_id,
|
||||
ds.update_ts,
|
||||
s.skill_gid
|
||||
FROM
|
|
@ -1,11 +1,14 @@
|
|||
SELECT
|
||||
d.name AS device_name,
|
||||
ds.id,
|
||||
ds.device_id,
|
||||
ds.install_failure_reason,
|
||||
ds.install_method,
|
||||
ds.install_status,
|
||||
ds.install_ts,
|
||||
ds.settings,
|
||||
ds.skill_id,
|
||||
ds.skill_settings_display_id,
|
||||
ds.update_ts,
|
||||
s.skill_gid
|
||||
FROM
|
||||
|
|
|
@ -0,0 +1,5 @@
|
|||
DELETE FROM
|
||||
device.device_skill
|
||||
WHERE
|
||||
device_id = %(device_id)s
|
||||
AND skill_id = (SELECT id FROM skill.skill WHERE skill_gid = %(skill_gid)s)
|
|
@ -0,0 +1,18 @@
|
|||
UPDATE
|
||||
device.device_skill
|
||||
SET
|
||||
install_method = %(install_method)s,
|
||||
install_status = %(install_status)s,
|
||||
install_failure_reason = %(failure_message)s,
|
||||
install_ts = %(install_ts)s,
|
||||
update_ts = %(update_ts)s
|
||||
WHERE
|
||||
device_id = %(device_id)s
|
||||
AND skill_id = (
|
||||
SELECT
|
||||
id
|
||||
FROM
|
||||
skill.skill
|
||||
WHERE
|
||||
skill_gid = %(skill_gid)s
|
||||
)
|
|
@ -1,4 +1,5 @@
|
|||
from .entity.display import SkillDisplay
|
||||
from .entity.skill import Skill
|
||||
from .entity.skill_setting import AccountSkillSetting
|
||||
from .repository.display import SkillDisplayRepository
|
||||
from .repository.setting import SkillSettingRepository
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
import json
|
||||
from datetime import datetime
|
||||
from typing import List
|
||||
|
||||
from selene.util.db import use_transaction
|
||||
|
@ -100,6 +99,13 @@ class SkillRepository(RepositoryBase):
|
|||
|
||||
return skills
|
||||
|
||||
def get_skill_by_global_id(self, skill_global_id):
|
||||
return self._select_one_into_dataclass(
|
||||
dataclass=Skill,
|
||||
sql_file_name='get_skill_by_global_id.sql',
|
||||
args=dict(skill_global_id=skill_global_id)
|
||||
)
|
||||
|
||||
@use_transaction
|
||||
def add(self, device_id: str, skill: dict) -> str:
|
||||
skill['skill_gid'] = skill.get('skill_gid') or skill.get('identifier')
|
||||
|
@ -127,44 +133,11 @@ class SkillRepository(RepositoryBase):
|
|||
result = '', ''
|
||||
return result
|
||||
|
||||
def update_skills_manifest(self, device_id: str, skill_manifest):
|
||||
for skill in skill_manifest:
|
||||
skill['device_id'] = device_id
|
||||
self._convert_to_datetime(skill)
|
||||
|
||||
db_batch_request = self._build_db_batch_request(
|
||||
'update_skill_manifest.sql',
|
||||
args=skill_manifest
|
||||
)
|
||||
self.cursor.batch_update(db_batch_request)
|
||||
|
||||
def _convert_to_datetime(self, skill):
|
||||
|
||||
installed = skill.get('installed')
|
||||
if installed and installed != 0:
|
||||
installed = datetime.fromtimestamp(installed)
|
||||
skill['installed'] = installed
|
||||
else:
|
||||
skill['installed'] = datetime.now()
|
||||
updated = skill.get('updated')
|
||||
if updated and updated != 0:
|
||||
updated = datetime.fromtimestamp(updated)
|
||||
skill['updated'] = updated
|
||||
else:
|
||||
skill['updated'] = datetime.now()
|
||||
failure_message = skill.get('failure_message')
|
||||
if failure_message is None:
|
||||
skill['failure_message'] = ''
|
||||
|
||||
def ensure_skill_exists(self, skill_gid: str) -> str:
|
||||
skill = self._select_one_into_dataclass(
|
||||
dataclass=Skill,
|
||||
sql_file_name='get_skill_by_global_id.sql',
|
||||
args=dict(skill_gid=skill_gid)
|
||||
)
|
||||
def ensure_skill_exists(self, skill_global_id: str) -> str:
|
||||
skill = self.get_skill_by_global_id(skill_global_id)
|
||||
if skill is None:
|
||||
family_name = _parse_skill_gid(skill_gid)
|
||||
skill_id = self._add_skill(skill_gid, family_name)
|
||||
family_name = _parse_skill_gid(skill_global_id)
|
||||
skill_id = self._add_skill(skill_global_id, family_name)
|
||||
else:
|
||||
skill_id = skill.id
|
||||
|
||||
|
@ -184,3 +157,10 @@ class SkillRepository(RepositoryBase):
|
|||
skill_id = db_result.id
|
||||
|
||||
return skill_id
|
||||
|
||||
def remove_by_gid(self, skill_gid):
|
||||
db_request = self._build_db_request(
|
||||
sql_file_name='remove_skill_by_gid.sql',
|
||||
args=dict(skill_gid=skill_gid)
|
||||
)
|
||||
self.cursor.delete(db_request)
|
||||
|
|
|
@ -4,4 +4,4 @@ SELECT
|
|||
FROM
|
||||
skill.skill
|
||||
WHERE
|
||||
skill_gid = %(skill_gid)s;
|
||||
skill_gid = %(skill_global_id)s;
|
||||
|
|
|
@ -0,0 +1,4 @@
|
|||
DELETE FROM
|
||||
skill.skill
|
||||
WHERE
|
||||
skill_gid = %(skill_gid)s
|
|
@ -1,23 +0,0 @@
|
|||
UPDATE
|
||||
device.device_skill dev_skill
|
||||
SET
|
||||
install_method = %(origin)s,
|
||||
install_status = %(installation)s,
|
||||
install_failure_reason = %(failure_message)s,
|
||||
install_ts = %(installed)s,
|
||||
update_ts = %(updated)s
|
||||
WHERE
|
||||
id IN (
|
||||
SELECT
|
||||
dev_skill.id
|
||||
FROM
|
||||
device.device dev
|
||||
INNER JOIN
|
||||
device.device_skill dev_skill ON dev.id = dev_skill.device_id
|
||||
INNER JOIN
|
||||
skill.skill skill ON dev_skill.skill_id = skill.id
|
||||
LEFT JOIN
|
||||
skill.display skill_disp ON skill.id = skill_disp.skill_id
|
||||
WHERE
|
||||
dev.id = %(device_id)s AND (skill.family_name = %(name)s or skill_disp.display_data->>'name' = %(name)s)
|
||||
)
|
|
@ -0,0 +1,25 @@
|
|||
from datetime import datetime
|
||||
from selene.data.device import ManifestSkill, DeviceSkillRepository
|
||||
|
||||
|
||||
def add_skill_to_manifest(db, device_id, skill):
|
||||
manifest_skill = ManifestSkill(
|
||||
device_id=device_id,
|
||||
install_method='test_install_method',
|
||||
install_status='test_install_status',
|
||||
skill_gid=skill.skill_gid,
|
||||
install_ts=datetime.utcnow(),
|
||||
update_ts=datetime.utcnow()
|
||||
)
|
||||
device_skill_repo = DeviceSkillRepository(db)
|
||||
manifest_skill.id = device_skill_repo.add_manifest_skill(
|
||||
skill.id,
|
||||
manifest_skill
|
||||
)
|
||||
|
||||
return manifest_skill
|
||||
|
||||
|
||||
def remove_manifest_skill(db, manifest_skill):
|
||||
device_skill_repo = DeviceSkillRepository(db)
|
||||
device_skill_repo.remove_manifest_skill(manifest_skill)
|
|
@ -0,0 +1,13 @@
|
|||
from selene.data.skill import Skill, SkillRepository
|
||||
|
||||
|
||||
def add_skill(db, skill_global_id):
|
||||
skill_repo = SkillRepository(db)
|
||||
skill_id = skill_repo.ensure_skill_exists(skill_global_id=skill_global_id)
|
||||
|
||||
return Skill(skill_global_id, skill_id)
|
||||
|
||||
|
||||
def remove_skill(db, skill_global_id):
|
||||
skill_repo = SkillRepository(db)
|
||||
skill_repo.remove_by_gid(skill_gid=skill_global_id)
|
Loading…
Reference in New Issue