ki 0.4.10 → 0.4.11

Sign up to get free protection for your applications and to get access to all the features.
Files changed (96) hide show
  1. checksums.yaml +4 -4
  2. data/Gemfile.lock +16 -8
  3. data/MIDDLEWARE.md +27 -7
  4. data/README.md +45 -2
  5. data/REALTIME.md +48 -0
  6. data/TODO.md +19 -0
  7. data/ki.gemspec +1 -0
  8. data/lib/ki.rb +5 -0
  9. data/lib/ki/base_request.rb +7 -0
  10. data/lib/ki/channel_manager.rb +58 -0
  11. data/lib/ki/ki.rb +1 -0
  12. data/lib/ki/ki_cli.rb +1 -1
  13. data/lib/ki/ki_config.rb +18 -2
  14. data/lib/ki/middleware/api_handler.rb +11 -16
  15. data/lib/ki/middleware/helpers/redirect_to_helper.rb +13 -0
  16. data/lib/ki/middleware/realtime.rb +81 -0
  17. data/lib/ki/model.rb +6 -4
  18. data/lib/ki/modules/restrictions.rb +4 -7
  19. data/lib/ki/orm.rb +38 -2
  20. data/lib/ki/utils/redirect_to_helper.rb +0 -0
  21. data/lib/ki/version.rb +1 -1
  22. data/spec/examples/json.northpole.ro/.bowerrc +3 -0
  23. data/spec/examples/json.northpole.ro/Gemfile +1 -0
  24. data/spec/examples/json.northpole.ro/bower.json +17 -0
  25. data/spec/examples/json.northpole.ro/config.ru +3 -0
  26. data/spec/examples/json.northpole.ro/config.yml +4 -0
  27. data/spec/examples/json.northpole.ro/config.yml.backup +18 -0
  28. data/spec/examples/json.northpole.ro/config/deploy.rb +2 -1
  29. data/spec/examples/json.northpole.ro/config/deploy/production.rb +4 -38
  30. data/spec/examples/json.northpole.ro/public/app/.bowerrc +3 -0
  31. data/spec/examples/json.northpole.ro/public/app/.gitignore +9 -0
  32. data/spec/examples/json.northpole.ro/public/app/.jshintrc +13 -0
  33. data/spec/examples/json.northpole.ro/public/app/.travis.yml +14 -0
  34. data/spec/examples/json.northpole.ro/public/app/LICENSE +22 -0
  35. data/spec/examples/json.northpole.ro/public/app/README.md +82 -0
  36. data/spec/examples/json.northpole.ro/public/app/bower.json +20 -0
  37. data/spec/examples/json.northpole.ro/public/app/e2e/pages/ContactUser.js +22 -0
  38. data/spec/examples/json.northpole.ro/public/app/e2e/pages/UserDetails.js +11 -0
  39. data/spec/examples/json.northpole.ro/public/app/e2e/pages/UserList.js +12 -0
  40. data/spec/examples/json.northpole.ro/public/app/e2e/protractor.conf.js +26 -0
  41. data/spec/examples/json.northpole.ro/public/app/e2e/scenarios/users.js +34 -0
  42. data/spec/examples/json.northpole.ro/public/app/gulpfile.js +104 -0
  43. data/spec/examples/json.northpole.ro/public/app/karma.conf.js +35 -0
  44. data/spec/examples/json.northpole.ro/public/app/package.json +30 -0
  45. data/spec/examples/json.northpole.ro/public/app/src/assets/svg/avatar-1.svg +11 -0
  46. data/spec/examples/json.northpole.ro/public/app/src/assets/svg/avatar-4.svg +16 -0
  47. data/spec/examples/json.northpole.ro/public/app/src/assets/svg/avatars.svg +244 -0
  48. data/spec/examples/json.northpole.ro/public/app/src/assets/svg/google_plus.svg +1 -0
  49. data/spec/examples/json.northpole.ro/public/app/src/assets/svg/hangouts.svg +1 -0
  50. data/spec/examples/json.northpole.ro/public/app/src/assets/svg/ic_fullscreen_48px.svg +4 -0
  51. data/spec/examples/json.northpole.ro/public/app/src/assets/svg/ic_fullscreen_exit_48px.svg +4 -0
  52. data/spec/examples/json.northpole.ro/public/app/src/assets/svg/ic_music_note_48px.svg +1 -0
  53. data/spec/examples/json.northpole.ro/public/app/src/assets/svg/ic_note_add_48px.svg +1 -0
  54. data/spec/examples/json.northpole.ro/public/app/src/assets/svg/ic_view_list_48px.svg +1 -0
  55. data/spec/examples/json.northpole.ro/public/app/src/assets/svg/icon.svg +1 -0
  56. data/spec/examples/json.northpole.ro/public/app/src/assets/svg/mail.svg +1 -0
  57. data/spec/examples/json.northpole.ro/public/app/src/assets/svg/manggo.svg +1095 -0
  58. data/spec/examples/json.northpole.ro/public/app/src/assets/svg/menu.svg +4 -0
  59. data/spec/examples/json.northpole.ro/public/app/src/assets/svg/phone.svg +1 -0
  60. data/spec/examples/json.northpole.ro/public/app/src/assets/svg/share.svg +3 -0
  61. data/spec/examples/json.northpole.ro/public/app/src/assets/svg/twitter.svg +1 -0
  62. data/spec/examples/json.northpole.ro/public/app/src/css/app.css +138 -0
  63. data/spec/examples/json.northpole.ro/public/app/src/css/app.css.map +7 -0
  64. data/spec/examples/json.northpole.ro/public/app/src/css/app.sass +145 -0
  65. data/spec/examples/json.northpole.ro/public/app/src/index.html +157 -0
  66. data/spec/examples/json.northpole.ro/public/app/src/js/MainController.coffee +167 -0
  67. data/spec/examples/json.northpole.ro/public/app/src/js/app.coffee +58 -0
  68. data/spec/examples/json.northpole.ro/public/app/src/js/blobs/BlobsController.coffee +115 -0
  69. data/spec/examples/json.northpole.ro/public/app/src/js/blobs/blobs.html +48 -0
  70. data/spec/examples/json.northpole.ro/public/app/src/js/tutorial/tutorial.html +15 -0
  71. data/spec/examples/json.northpole.ro/public/app/src/js/users/UserService.coffee +12 -0
  72. data/spec/examples/json.northpole.ro/public/app/src/js/users/Users.coffee +1 -0
  73. data/spec/examples/json.northpole.ro/public/app/src/js/users/users.html +8 -0
  74. data/spec/examples/json.northpole.ro/public/javascripts/jnorthpole.coffee +40 -7
  75. data/spec/examples/json.northpole.ro/public/javascripts/realtime.coffee +28 -0
  76. data/spec/examples/json.northpole.ro/public/{javascripts/music → music}/angular-youtube-embed.js +0 -0
  77. data/spec/examples/json.northpole.ro/public/music/index.html +126 -0
  78. data/spec/examples/json.northpole.ro/public/music/music.coffee +99 -0
  79. data/spec/examples/json.northpole.ro/public/music/music.sass +63 -0
  80. data/spec/examples/json.northpole.ro/public/stylesheets/app.sass +3 -0
  81. data/spec/examples/json.northpole.ro/views/faq.haml +7 -0
  82. data/spec/examples/json.northpole.ro/views/layout.haml +1 -0
  83. data/spec/examples/json.northpole.ro/views/music.haml +2 -0
  84. data/spec/examples/json.northpole.ro/views/websocket.haml +7 -0
  85. data/spec/lib/ki/channel_manager_spec.rb +82 -0
  86. data/spec/lib/ki/helpers_spec.rb +11 -0
  87. data/spec/lib/ki/ki_config_spec.rb +28 -0
  88. data/spec/lib/ki/middleware/admin_generator_spec.rb +8 -0
  89. data/spec/lib/ki/middleware/init_middleware_spec.rb +21 -0
  90. data/spec/lib/ki/middleware/realtime_spec.rb +96 -0
  91. data/spec/lib/ki/model_spec.rb +28 -7
  92. data/spec/lib/ki/modules/model_helper_spec.rb +31 -0
  93. data/spec/lib/ki/orm_spec.rb +26 -0
  94. metadata +211 -9
  95. data/spec/examples/json.northpole.ro/public/javascripts/docs.min.js +0 -16
  96. data/spec/examples/json.northpole.ro/views/awsum.haml +0 -108
@@ -0,0 +1,28 @@
1
+ $(document).ready ->
2
+ receivedMessages = []
3
+
4
+ output = $('.realtime-output')
5
+ return if output.length <= 0
6
+
7
+ socket = jNorthPole.getNewRealtimeSocket((data) ->
8
+ console.log data
9
+ return unless data.data?
10
+ json = JSON.parse(data.data)
11
+ return unless json.messages?
12
+
13
+ for message in json.messages
14
+ if $.inArray(message.id, receivedMessages) == -1
15
+ receivedMessages.push(message.id)
16
+ output.append("#{message.content.message}<br />")
17
+ )
18
+ setTimeout ->
19
+ jNorthPole.subscribe(socket, 'jNorthPoleChat')
20
+ , 2000
21
+
22
+ $('.realtime-input').keypress((event) ->
23
+ keycode = if event.keyCode then event.keyCode else event.which
24
+ if (keycode == 13)
25
+ inptz = $(@)
26
+ jNorthPole.publish(socket, 'jNorthPoleChat', { message: inptz.val() })
27
+ inptz.val('')
28
+ )
@@ -0,0 +1,126 @@
1
+ <!DOCTYPE html>
2
+ <html ng-app="app">
3
+ <head>
4
+ <meta charset="utf-8">
5
+ <meta name="viewport" content="width=device-width">
6
+ <title>music</title>
7
+
8
+ <link href="//maxcdn.bootstrapcdn.com/bootstrap/3.3.2/css/bootstrap.min.css" rel="stylesheet">
9
+ <!-- Include roboto.css to use the Roboto web font, material.css to include the theme and ripples.css to style the ripple effect -->
10
+ <link href="/bootstrap-material-design/dist/css/roboto.min.css" rel="stylesheet">
11
+ <link href="/bootstrap-material-design/dist/css/material.min.css" rel="stylesheet">
12
+ <link href="/bootstrap-material-design/dist/css/ripples.min.css" rel="stylesheet">
13
+
14
+ <link rel="stylesheet" href="//maxcdn.bootstrapcdn.com/font-awesome/4.4.0/css/font-awesome.min.css">
15
+ <link rel="stylesheet" href="/music/music.css">
16
+ </head>
17
+ <body ng-controller="MainCtrl">
18
+ <div class="container">
19
+ <div class="row" ng-hide="!loggedIn">
20
+ <div class="col-md-12 well" style="display: flex; align-items: center;">
21
+ <div class="col-xs-4 col-md-2 col-md-offset-6">
22
+ <div class="form-group has-warning">
23
+ <input type="text" class="form-control" ng-model="search" placeholder="search"/>
24
+ </div>
25
+ </div>
26
+ <div class="col-xs-2 col-sm-1 col-sm-offset-6 col-md-offset-2 col-xs-offset-4">
27
+ <a ng-click="playRandom()" class="btn btn-danger btn-fab btn-raised fa fa-random"></a>
28
+ </div>
29
+ <div class="col-xs-2 col-sm-1">
30
+ <a ng-click="refresh()" class="btn btn-danger btn-fab btn-raised fa" ng-class="{ 'fa-sign-in': !loggedIn, 'fa-sign-out': loggedIn}"></a>
31
+ </div>
32
+ </div>
33
+ </div>
34
+ <div class="row" ng-show="!loggedIn">
35
+ <div class="well col-xs-8 col-xs-offset-2 col-sm-6 col-sm-offset-3 col-md-4 col-md-offset-4">
36
+ <form class="form-horizontal">
37
+ <fieldset>
38
+ <!-- <legend>Sign in</legend> -->
39
+ <div class="form-group has-warning">
40
+ <!-- <label for="inputApiKey" class="col-lg-2 control-label">api_key</label> -->
41
+ <div class="col-lg-12">
42
+ <input type="text" class="form-control input-lg" ng-model="json.api_key" id="inputApiKey" placeholder="api_key">
43
+ </div>
44
+ <!-- <label for="inputSecret" class="col-lg-2 control-label">secret</label> -->
45
+ <div class="col-lg-12">
46
+ <input type="password" class="form-control input-lg" ng-model="json.secret" id="inputSecret" placeholder="secret">
47
+ </div>
48
+ </div>
49
+ <div class="form-group">
50
+ <div class="col-lg-10 col-lg-offset-2 text-center">
51
+ <button type="submit" class="btn btn-danger" ng-click="refresh()">login</button>
52
+ </div>
53
+ </div>
54
+ </fieldset>
55
+ </form>
56
+ </div>
57
+ </div>
58
+
59
+ <div class="row" ng-show="loggedIn">
60
+ <div class="col-md-6">
61
+ <div class="embed-responsive embed-responsive-16by9 well">
62
+ <youtube-video class="embed-responsive-item" video-url="ytVideoUrl" player="bestPlayer" player-vars="playerVars"></youtube-video>
63
+ </div>
64
+ </div>
65
+ <div class="col-md-6">
66
+ <div class="list-group well">
67
+ <div ng-repeat="result in results | filter: search">
68
+ <div class="list-group-item vertical-center" ng-class="{ selected: isSelected(result) }">
69
+ <div class="row-action-primary">
70
+ <i class="fa fa-edit" ng-click="select(result)" data-toggle="modal" data-target="#complete-dialog"></i>
71
+ </div>
72
+ <div class="row-content" style="display: flex; align-items: center;">
73
+ <div class="least-content row-action-primary">
74
+ </div>
75
+ <div ng-click="play(result)">
76
+ <h4 class="list-group-item-heading no-wrap">{{ result.name || result}}</h4>
77
+ <p class="disabled list-group-item-text no-wrap disabled">{{ result.url }}</p>
78
+ </div>
79
+ </div>
80
+ </div>
81
+ <div class="list-group-separator"></div>
82
+ </div>
83
+ </div>
84
+ </div>
85
+ </div>
86
+ <div ng-show="loggedIn">
87
+ <a ng-click="newSong()" data-toggle="modal" data-target="#complete-dialog" class="btn btn-danger btn-fab btn-raised fa fa-plus add-button"></a>
88
+ </div>
89
+
90
+ <div id="complete-dialog" class="modal fade" tabindex="-1">
91
+ <div class="modal-dialog modal-sm">
92
+ <div class="modal-content">
93
+ <div class="modal-header">
94
+ <!-- <button type="button" class="close" data-dismiss="modal" aria-hidden="true">×</button> -->
95
+ <div class="form-group has-warning">
96
+ </div>
97
+ </div>
98
+ <div class="modal-body">
99
+ <div class="form-group has-warning">
100
+ <input class="form-control" ng-model="selected.name" placeholder="title" />
101
+ <input class="form-control" ng-model="selected.url" placeholder="url" />
102
+ <input class="form-control" ng-model="selected.moods" placeholder="moods" ng-list required>
103
+ </div>
104
+ </div>
105
+ <div class="modal-footer">
106
+ <button type="button" class="btn btn-default" data-dismiss="modal">Close</button>
107
+ <button type="button" class="btn btn-danger" ng-click="save(selected)">Save changes</button>
108
+ </div>
109
+ </div>
110
+ </div>
111
+ </div>
112
+
113
+ <script src="//ajax.googleapis.com/ajax/libs/angularjs/1.3.14/angular.min.js"></script>
114
+ <script src="//code.jquery.com/jquery-1.10.2.min.js"></script>
115
+ <script src="//maxcdn.bootstrapcdn.com/bootstrap/3.3.2/js/bootstrap.min.js"></script>
116
+
117
+ <script src="/bootstrap-material-design/dist/js/ripples.min.js"></script>
118
+ <script src="/bootstrap-material-design/dist/js/material.min.js"></script>
119
+
120
+ <script src="//www.youtube.com/iframe_api"></script>
121
+ <script src="/music/angular-youtube-embed.js"></script>
122
+
123
+ <script src="/javascripts/jnorthpole.js"></script>
124
+ <script src="/music/music.js"></script>
125
+ </body>
126
+ </html>
@@ -0,0 +1,99 @@
1
+ $(() ->
2
+ $.material.init()
3
+ )
4
+
5
+ app = angular.module('app', [ 'youtube-embed' ])
6
+
7
+ app.controller 'MainCtrl', ($scope, youtubeEmbedUtils) ->
8
+
9
+ $scope.refresh = ->
10
+ if $scope.loggedIn
11
+ $scope.loggedIn = false
12
+ $scope.results = []
13
+ localStorage.clear()
14
+ $scope.json =
15
+ 'api_key': 'guest'
16
+ 'secret': 'guest'
17
+ 'category': 'music'
18
+ else
19
+ $scope.loggedIn = false
20
+ jNorthPole.getStorage $scope.json, (data) ->
21
+ if $scope.json.apiKey != 'guest' and !data.error?
22
+ localStorage.json = angular.toJson($scope.json)
23
+ unless data.error? and data.api_key != 'guest'
24
+ $scope.apiKey = $scope.json.apiKey
25
+ $scope.results = data
26
+ $scope.loggedIn = true
27
+ $scope.$apply()
28
+ return
29
+
30
+ return
31
+
32
+ jNorthPole.BASE_URL = 'https://json.northpole.ro/'
33
+ $scope.results = []
34
+ $scope.playerVars =
35
+ controls: 1
36
+ autoplay: 1
37
+ if localStorage.json == undefined
38
+ $scope.json =
39
+ 'api_key': 'guest'
40
+ 'secret': 'guest'
41
+ 'category': 'music'
42
+ else
43
+ $scope.json = angular.fromJson(localStorage.json)
44
+ $scope.refresh()
45
+
46
+ $scope.play = (video) ->
47
+ return unless video?
48
+ if $scope.ytVideoUrl == video.url and $scope.bestPlayer? and $scope.bestPlayer.getPlayerState() == 1
49
+ $scope.bestPlayer.pauseVideo()
50
+ else
51
+ $scope.ytVideoUrl = video.url
52
+ $scope.bestPlayer.playVideo() if $scope.bestPlayer?
53
+ # console.log youtubeEmbedUtils.getIdFromURL($scope.ytVideoUrl)
54
+ return
55
+
56
+ $scope.edit = (video) ->
57
+ $scope.selected = video
58
+ return
59
+
60
+ $scope.save = (video) ->
61
+ $scope.selected.secret = $scope.json.secret
62
+ json = angular.copy($scope.selected)
63
+
64
+ log = (data) ->
65
+ console.log data
66
+ return
67
+
68
+ if video.id?
69
+ jNorthPole.putStorage json, log
70
+ else
71
+ json.api_key = $scope.json.api_key
72
+ json.category = 'music'
73
+ jNorthPole.createStorage json, log
74
+ $('#complete-dialog').modal('hide')
75
+ $scope.selected = undefined
76
+ return
77
+
78
+ $scope.newSong = ->
79
+ $scope.selected = {}
80
+ return
81
+
82
+ $scope.select = (json) ->
83
+ $scope.selected = json
84
+ return
85
+
86
+ $scope.isSelected = (video) ->
87
+ $scope.ytVideoUrl == video.url
88
+
89
+ $scope.playRandom = ->
90
+ item = $scope.results[Math.floor(Math.random() * $scope.results.length)]
91
+ $scope.play item
92
+ if player?
93
+ player.seekTo 0
94
+ return
95
+
96
+ $scope.$on 'youtube.player.ended', ($event, player) ->
97
+ $scope.playRandom player
98
+ return
99
+ return
@@ -0,0 +1,63 @@
1
+ $size: 56px
2
+
3
+ html, body
4
+ height: 100%
5
+
6
+ .bear
7
+ background: url(/images/bear.png) no-repeat center center
8
+
9
+ .embed-responsive
10
+ position: relative
11
+ display: block
12
+ height: 0
13
+ padding: 0
14
+ overflow: hidden
15
+
16
+ .embed-responsive.embed-responsive-16by9
17
+ padding-bottom: 56.25%
18
+
19
+ .embed-responsive-item
20
+ @extend .bear
21
+ position: absolute
22
+ top: 0
23
+ bottom: 0
24
+ left: 0
25
+ width: 100%
26
+ height: 100%
27
+ border: 0
28
+
29
+ .container
30
+ z-index: 10
31
+
32
+ .content-container
33
+ padding-top: $size + 10px
34
+ padding-bottom: $size - 10px
35
+
36
+ .add-button
37
+ margin-bottom: 10px !important
38
+ margin-right: 10px !important
39
+ position: fixed
40
+ right: 0
41
+ bottom: 0
42
+
43
+ .no-wrap
44
+ white-space: nowrap
45
+ overflow: hidden
46
+ text-overflow: ellipsis
47
+
48
+ .vertical-center
49
+ display: flex
50
+ align-items: center
51
+
52
+ p.disabled
53
+ color: #bdbdbd
54
+
55
+ .selected
56
+ background-color: red !important
57
+ p
58
+ color: #f5f5f5
59
+ h4
60
+ color: #f5f5f5 !important
61
+
62
+ .list-group-separator
63
+ background-color: white
@@ -45,3 +45,6 @@ input[type="text"], textarea
45
45
 
46
46
  .form-control:focus
47
47
  border-color: #ccc
48
+
49
+ .realtime-input
50
+ background-color: #f5f5f5
@@ -8,6 +8,8 @@
8
8
  %br
9
9
  %a{href: '#register'} How do I register?
10
10
  %br
11
+ %a{href: '#realtime'} Is there an experimental realtime module?
12
+ %br
11
13
  %h3#what What is json.northpole.ro?
12
14
  %p
13
15
  json.northpole.ro is a JSON cloud storage service.
@@ -36,3 +38,8 @@
36
38
  %a{href: '/playground'} playground
37
39
  \. If you can't do that, there is a good chance you don't understand
38
40
  how the API works. Think of it as a tutorial.
41
+ %h3#realtime Is there an experimental realtime module?
42
+ %p
43
+ Yes. Go to
44
+ %a{href: '/websocket'} /websocket
45
+ to give it a spin.
@@ -41,4 +41,5 @@
41
41
  = js "https://ajax.googleapis.com/ajax/libs/jquery/1.11.0/jquery.min.js"
42
42
  = js "//netdna.bootstrapcdn.com/bootstrap/3.1.1/js/bootstrap.min.js"
43
43
  = js "/javascripts/jnorthpole.js"
44
+ = js "/javascripts/realtime.js"
44
45
  = js "/javascripts/app.js"
@@ -0,0 +1,2 @@
1
+ :javascript
2
+ window.location.href = "/music/index.html";
@@ -0,0 +1,7 @@
1
+ %p
2
+ You are subscribed to the
3
+ %b jNorthPoleChat
4
+ channel. Everybody in this channel will be able to view your messages.
5
+ %input.realtime-input.form-control{ type: 'text', placeholder: 'write your message and press enter' }
6
+ %br
7
+ %pre.realtime-output
@@ -0,0 +1,82 @@
1
+ require 'spec_helper'
2
+
3
+ describe Ki::ChannelManager do
4
+ let(:cm) { Ki::ChannelManager }
5
+ let(:db) { Ki::Orm::Db.instance }
6
+
7
+ it 'returns the db connection' do
8
+ expect(cm.db).to eq db
9
+ end
10
+
11
+ it 'stores a socket on connection' do
12
+ expect {
13
+ cm.connect
14
+ }.to change { db.count 'realtime_channel_sockets' }.by(1)
15
+ end
16
+
17
+ it 'removes a channel socket on disconnect' do
18
+ socket = cm.connect
19
+ expect {
20
+ cm.disconnect(socket)
21
+ }.to change { db.count 'realtime_channel_sockets' }.by(-1)
22
+ end
23
+
24
+ it 'removes all the channel subscriptions on disconnect' do
25
+ socket = cm.connect
26
+ cm.subscribe(socket_id: socket['id'], type: 'subscribe', 'channel_name' => 'test-channel')
27
+ cm.subscribe(socket_id: socket['id'], type: 'subscribe', 'channel_name' => 'test-channel-2')
28
+ expect {
29
+ cm.disconnect(socket)
30
+ }.to change { db.count 'realtime_channel_subscriptions' }.by(-2)
31
+ end
32
+
33
+ it 'returns the list of channel sockets' do
34
+ cm.cleanup
35
+ expect(cm.sockets).to be_empty
36
+
37
+ cm.connect
38
+ expect(cm.sockets).to_not be_empty
39
+ end
40
+
41
+ it 'subscribes to a channel' do
42
+ socket = cm.connect
43
+
44
+ expect {
45
+ cm.subscribe(socket_id: socket['id'], type: 'subscribe', 'channel_name' => 'test-channel')
46
+ }.to change { db.count 'realtime_channel_subscriptions' }.by(1)
47
+ end
48
+
49
+ it 'does not subscribe to the same channel twice' do
50
+ socket = cm.connect
51
+
52
+ expect {
53
+ cm.subscribe(socket_id: socket['id'], type: 'subscribe', 'channel_name' => 'test-channel')
54
+ cm.subscribe(socket_id: socket['id'], type: 'subscribe', 'channel_name' => 'test-channel')
55
+ }.to change { db.count 'realtime_channel_subscriptions' }.by(1)
56
+ end
57
+
58
+ it 'unsubscribes fom a channel' do
59
+ socket = cm.connect
60
+ cm.subscribe(socket_id: socket['id'], type: 'subscribe', 'channel_name' => 'test-channel')
61
+
62
+ expect {
63
+ cm.unsubscribe(socket_id: socket['id'], 'type' => 'unsubscribe', 'channel_name' => 'test-channel')
64
+ }.to change { db.count 'realtime_channel_subscriptions' }.by(-1)
65
+ end
66
+
67
+ it 'returns messages from a tick'
68
+
69
+ it 'publishes a message' do
70
+ expect {
71
+ item = cm.publish(hello: 'world')
72
+ expect(item['created_at']).to_not be_nil # TODO: might need present?
73
+ }.to change { db.count 'realtime_channel_messages' }.by(1)
74
+ end
75
+
76
+ it 'cleans up the data' do
77
+ cm.cleanup
78
+ expect(db.count('realtime_channel_sockets')).to eq 0
79
+ expect(db.count('realtime_channel_subscriptions')).to eq 0
80
+ expect(db.count('realtime_channel_messages')).to eq 0
81
+ end
82
+ end