netologiest 0.0.0 → 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/README.md +85 -0
- data/lib/netologiest.rb +7 -0
- data/lib/netologiest/config.rb +9 -0
- data/lib/netologiest/exceptions.rb +3 -0
- data/lib/netologiest/resource.rb +97 -0
- data/lib/netologiest/resources/course.rb +28 -0
- data/lib/netologiest/version.rb +3 -2
- data/netologiest.gemspec +2 -1
- data/spec/course_spec.rb +54 -0
- data/spec/fixtures/auth.json +4 -0
- data/spec/fixtures/course.json +422 -0
- data/spec/fixtures/courses.json +57 -0
- data/spec/fixtures/netologiest_test.yml +2 -0
- data/spec/helpers/webmock_helpers.rb +79 -0
- data/spec/resource_spec.rb +11 -0
- data/spec/spec_helper.rb +16 -0
- metadata +38 -4
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 8b6e1d7e36a3857d851d1bcc54007f3aeddfe753
|
4
|
+
data.tar.gz: 7b0f8a1fc72547248c13c4f9a93ddf485af85131
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: dddf7b03ca6142c6b97ded47c1f8472ec8e534c7163a22edf2f17a8c214f01e00af1b782078dc2aa11301c614c380faf9a79886066f01c8bf6370e8a055d1892
|
7
|
+
data.tar.gz: 371b2fdab4d38087abe607cfa3c2fbd90a659d80090b89a53a2a887e508134ceadfbf125546ccf9628dd17b732020c5b0097e01ebc6767d5966df779509fbd78
|
data/README.md
CHANGED
@@ -1,2 +1,87 @@
|
|
1
|
+
[](https://travis-ci.org/teachbase/netologiest)
|
2
|
+
|
1
3
|
# Netologiest
|
2
4
|
Netology Ruby API client
|
5
|
+
|
6
|
+
API for Netology (http://netology.ru/) e-learning intergration.
|
7
|
+
|
8
|
+
## Configuration
|
9
|
+
|
10
|
+
Netologiest uses [anyway_config](https://github.com/palkan/anyway_config) to configure client.
|
11
|
+
|
12
|
+
It has two configuration attributes:
|
13
|
+
- `api_key`;
|
14
|
+
- `api_url`.
|
15
|
+
|
16
|
+
For example (in your `secrets.yml`):
|
17
|
+
|
18
|
+
```ruby
|
19
|
+
....
|
20
|
+
netologiest:
|
21
|
+
api_key: "your_api_key_here"
|
22
|
+
api_url: "http://dev.netology.ru/content_api"
|
23
|
+
....
|
24
|
+
```
|
25
|
+
|
26
|
+
## Usage
|
27
|
+
|
28
|
+
Netologiest having only one resource at moment (Course).
|
29
|
+
|
30
|
+
To get a list of courses
|
31
|
+
|
32
|
+
```ruby
|
33
|
+
Netologiest::Course.list
|
34
|
+
|
35
|
+
#=>
|
36
|
+
[
|
37
|
+
{
|
38
|
+
"id" => "1",
|
39
|
+
"name" => "Direct marketing. Basics.",
|
40
|
+
"last_updated_at" => "1387176082"
|
41
|
+
},
|
42
|
+
{
|
43
|
+
"id" => "2",
|
44
|
+
"name" => "Test course #1",
|
45
|
+
"last_updated_at" => "1386236837"
|
46
|
+
},
|
47
|
+
{
|
48
|
+
"id" => "3",
|
49
|
+
"name" => "How to make course.",
|
50
|
+
"last_updated_at" => "1387176130"
|
51
|
+
}
|
52
|
+
]
|
53
|
+
```
|
54
|
+
|
55
|
+
Also you can get detailed information about any course:
|
56
|
+
|
57
|
+
```ruby
|
58
|
+
# argument is Course ID
|
59
|
+
Netologiest::Course.detail(1)
|
60
|
+
|
61
|
+
#=>
|
62
|
+
{
|
63
|
+
"id" => 931,
|
64
|
+
"name" => "Name of course",
|
65
|
+
"description" => "Description of course",
|
66
|
+
"progress" => 0,
|
67
|
+
"duration" => 47,
|
68
|
+
"level" => { ... },
|
69
|
+
"tags" => [{..}, {..}, {..}]
|
70
|
+
....
|
71
|
+
"blocks" => [
|
72
|
+
...
|
73
|
+
{
|
74
|
+
"lessons" => []
|
75
|
+
}
|
76
|
+
...
|
77
|
+
]
|
78
|
+
}
|
79
|
+
```
|
80
|
+
|
81
|
+
## Contributing
|
82
|
+
|
83
|
+
1. Fork it ( https://github.com/[my-github-username]/netologiest/fork )
|
84
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
85
|
+
3. Commit your changes (`git commit -am 'Add some feature'`)
|
86
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
87
|
+
5. Create a new Pull Request
|
data/lib/netologiest.rb
CHANGED
@@ -1,5 +1,12 @@
|
|
1
|
+
require 'netologiest/resource'
|
2
|
+
require 'netologiest/resources/course'
|
3
|
+
require 'netologiest/config'
|
4
|
+
require 'netologiest/exceptions'
|
1
5
|
require 'netologiest/version'
|
2
6
|
|
3
7
|
# Ruby client for Netology API
|
4
8
|
module Netologiest
|
9
|
+
def self.config
|
10
|
+
@config ||= Config.new
|
11
|
+
end
|
5
12
|
end
|
@@ -0,0 +1,97 @@
|
|
1
|
+
require 'json'
|
2
|
+
require 'rest-client'
|
3
|
+
require 'active_support'
|
4
|
+
|
5
|
+
module Netologiest
|
6
|
+
# This class describe client for
|
7
|
+
# Netology API. It contains finder actions
|
8
|
+
# and special handlers methods
|
9
|
+
class Resource
|
10
|
+
class << self
|
11
|
+
attr_accessor :resource_name
|
12
|
+
end
|
13
|
+
|
14
|
+
attr_reader :token, :token_expire
|
15
|
+
|
16
|
+
def initialize
|
17
|
+
authorize!
|
18
|
+
end
|
19
|
+
|
20
|
+
def self.list
|
21
|
+
new.list
|
22
|
+
end
|
23
|
+
|
24
|
+
def self.detail(id)
|
25
|
+
new.detail(id)
|
26
|
+
end
|
27
|
+
|
28
|
+
def list
|
29
|
+
handle_list(
|
30
|
+
get(
|
31
|
+
build_url(self.class.resource_name)
|
32
|
+
)
|
33
|
+
)
|
34
|
+
end
|
35
|
+
|
36
|
+
def detail(id)
|
37
|
+
handle_detail(
|
38
|
+
get(
|
39
|
+
build_url(self.class.resource_name, id)
|
40
|
+
)
|
41
|
+
)
|
42
|
+
end
|
43
|
+
|
44
|
+
# rubocop:disable Metrics/AbcSize, Metrics//MethodLength
|
45
|
+
def authorize!
|
46
|
+
url = build_url('gettoken')
|
47
|
+
params = { client_secret: Netologiest.config.api_key }
|
48
|
+
RestClient.get(url, params: params) do |response, _request, _result|
|
49
|
+
case response.code
|
50
|
+
when 200
|
51
|
+
body = JSON.parse(response.body)
|
52
|
+
@token_expire = Time.now.to_i + body.fetch('expires_in').to_i
|
53
|
+
@token = body['access_token']
|
54
|
+
when 401
|
55
|
+
fail Netologiest::Unauthorized, response.body
|
56
|
+
else
|
57
|
+
response
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
# rubocop:enable Metrics/AbcSize, Metrics//MethodLength
|
62
|
+
|
63
|
+
def token_expired?
|
64
|
+
return true unless token_expire.present?
|
65
|
+
token_expire < Time.now.to_i
|
66
|
+
end
|
67
|
+
|
68
|
+
def handle_list(_response); end
|
69
|
+
|
70
|
+
def handle_detail(_response); end
|
71
|
+
|
72
|
+
protected
|
73
|
+
|
74
|
+
def build_url(*args)
|
75
|
+
File.join(Netologiest.config.api_url, *args.map(&:to_s))
|
76
|
+
end
|
77
|
+
|
78
|
+
# rubocop:disable Metrics/MethodLength
|
79
|
+
def get(url, options = {})
|
80
|
+
params = { token: token }.merge!(options)
|
81
|
+
|
82
|
+
RestClient.get(url, params: params) do |response, _request, _result|
|
83
|
+
if response.code == 401
|
84
|
+
begin
|
85
|
+
authorize!
|
86
|
+
params[:token] = token
|
87
|
+
return RestClient.get(url, params: params)
|
88
|
+
rescue RestClient::Unauthorized
|
89
|
+
raise Netologiest::Unauthorized, response.body
|
90
|
+
end
|
91
|
+
end
|
92
|
+
response
|
93
|
+
end
|
94
|
+
end
|
95
|
+
# rubocop:enable Metrics/MethodLength
|
96
|
+
end
|
97
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
module Netologiest
|
2
|
+
# Child class for Resource
|
3
|
+
# describe rules for parsing Netology response
|
4
|
+
class Course < Netologiest::Resource
|
5
|
+
self.resource_name = 'courses'
|
6
|
+
|
7
|
+
# Methods for parsing JSON response
|
8
|
+
# it returns Array of Hash instances
|
9
|
+
def handle_list(response)
|
10
|
+
parse_json(response)
|
11
|
+
end
|
12
|
+
|
13
|
+
# it returns a Hash instance
|
14
|
+
# it contains deep elements:
|
15
|
+
# => blocks: [{lessons: [{questions: []}]]
|
16
|
+
def handle_detail(response)
|
17
|
+
parse_json(response)
|
18
|
+
end
|
19
|
+
|
20
|
+
private
|
21
|
+
|
22
|
+
def parse_json(response)
|
23
|
+
data = JSON.parse(response)
|
24
|
+
return unless data.present? || data.any?
|
25
|
+
data
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
data/lib/netologiest/version.rb
CHANGED
data/netologiest.gemspec
CHANGED
@@ -24,6 +24,7 @@ Gem::Specification.new do |spec|
|
|
24
24
|
spec.add_development_dependency "webmock"
|
25
25
|
spec.add_development_dependency 'pry'
|
26
26
|
spec.add_dependency "anyway_config", "~> 0", ">= 0.3"
|
27
|
-
spec.add_dependency
|
27
|
+
spec.add_dependency "rest-client"
|
28
|
+
spec.add_dependency 'activesupport'
|
28
29
|
spec.add_dependency 'json'
|
29
30
|
end
|
data/spec/course_spec.rb
ADDED
@@ -0,0 +1,54 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
|
3
|
+
describe Netologiest::Course do
|
4
|
+
describe "valid response" do
|
5
|
+
before(:each) do
|
6
|
+
mock_api
|
7
|
+
courses_stub
|
8
|
+
end
|
9
|
+
|
10
|
+
it "return list of courses" do
|
11
|
+
courses = described_class.list
|
12
|
+
expect(courses.count).to eq 11
|
13
|
+
expect(courses.first["id"]).to eq "426"
|
14
|
+
end
|
15
|
+
|
16
|
+
it "return detail hash of course" do
|
17
|
+
detail_course_stub(931)
|
18
|
+
course = described_class.detail(931)
|
19
|
+
expect(course["id"]).to eq 931
|
20
|
+
expect(course["name"]).to eq "Контекстная реклама в Google AdWords"
|
21
|
+
expect(course["blocks"].size).to eq 1
|
22
|
+
expect(course["blocks"][0]["lessons"].size).to eq 11
|
23
|
+
end
|
24
|
+
|
25
|
+
it "return nil if course not found" do
|
26
|
+
empty_course_stub(1)
|
27
|
+
course = described_class.detail(1)
|
28
|
+
expect(course).to be_nil
|
29
|
+
end
|
30
|
+
|
31
|
+
it "many authorize requests" do
|
32
|
+
many_requests_stub(931)
|
33
|
+
course = described_class.detail(931)
|
34
|
+
expect(course["id"]).to eq 931
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
describe "errors" do
|
39
|
+
it "raise error if token is bad" do
|
40
|
+
mock_api
|
41
|
+
bad_token_stub(1)
|
42
|
+
expect { described_class.detail(1) }
|
43
|
+
.to raise_error(Netologiest::Unauthorized, /Need token/)
|
44
|
+
end
|
45
|
+
|
46
|
+
it "raise error if secret key is bad" do
|
47
|
+
auth_url = "#{Netologiest.config.api_url}/gettoken"
|
48
|
+
url = "#{auth_url}?client_secret=#{Netologiest.config.api_key}"
|
49
|
+
auth_failed_stub(url)
|
50
|
+
expect { described_class.detail(931) }
|
51
|
+
.to raise_error(Netologiest::Unauthorized, /Need correct client_secret/)
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
@@ -0,0 +1,422 @@
|
|
1
|
+
{
|
2
|
+
"id": 931,
|
3
|
+
"name": "Контекстная реклама в Google AdWords",
|
4
|
+
"description": "<p>Почему Google AdWords обходится дешевле, чем «Яндекс. Директ»? Как сберечь деньги на контекстной рекламе и привлечь целевой трафик на сайт?<br />\r\n <br />\r\nКурс адресован начинающим специалистам по контекстной рекламе и интернет-маркетологам, которые знают основы медиапланирования.</p>\r\n\r\n<p>Полина Аверина расскажет, как создавать рекламные кампании в Google AdWords для поисковой и контекстно-медийной сети. Вы научитесь работать со ссылками, составлять эффективные объявления, подбирать ключевые фразы и минус-слова. Узнаете, как с помощью ремаркетинга вернуть заинтересованных пользователей.</p>\r\n",
|
5
|
+
"progress": 0,
|
6
|
+
"duration": 47,
|
7
|
+
"level": {
|
8
|
+
"id": 1,
|
9
|
+
"name": "Базовый"
|
10
|
+
},
|
11
|
+
"tags": [
|
12
|
+
{
|
13
|
+
"id": 4,
|
14
|
+
"name": "Интернет-маркетинг"
|
15
|
+
}
|
16
|
+
],
|
17
|
+
"authors": [
|
18
|
+
{
|
19
|
+
"id": 2426656,
|
20
|
+
"organization_post": "Руководитель отдела контекстной рекламы сервиса 1PS",
|
21
|
+
"full_name": "Полина Аверина",
|
22
|
+
"image": "http://netology.ru/content/u7/7049.jpeg",
|
23
|
+
"description": ""
|
24
|
+
}
|
25
|
+
],
|
26
|
+
"blocks": [
|
27
|
+
{
|
28
|
+
"id": 931,
|
29
|
+
"name": "Блок по умолчанию",
|
30
|
+
"position": 1,
|
31
|
+
"lessons": [
|
32
|
+
{
|
33
|
+
"id": 2268,
|
34
|
+
"name": "Вступление и знакомство с автором курса",
|
35
|
+
"position": 1,
|
36
|
+
"human_type": "video_lesson",
|
37
|
+
"poster": "http://netology.ru/content/p3/3375@2x.png",
|
38
|
+
"video_url": "/courses/931/lessons/2268/iframe"
|
39
|
+
},
|
40
|
+
{
|
41
|
+
"id": 2269,
|
42
|
+
"name": "Сравнение Google AdWords и «Яндекс.Директа»",
|
43
|
+
"position": 2,
|
44
|
+
"human_type": "video_lesson",
|
45
|
+
"poster": "http://netology.ru/content/p3/3375@2x.png",
|
46
|
+
"video_url": "/courses/931/lessons/2269/iframe"
|
47
|
+
},
|
48
|
+
{
|
49
|
+
"id": 2270,
|
50
|
+
"name": "Настройки поисковой кампании",
|
51
|
+
"position": 3,
|
52
|
+
"human_type": "video_lesson",
|
53
|
+
"poster": "http://netology.ru/content/p3/3375@2x.png",
|
54
|
+
"video_url": "/courses/931/lessons/2270/iframe"
|
55
|
+
},
|
56
|
+
{
|
57
|
+
"id": 2271,
|
58
|
+
"name": "Работа с ключевыми словами",
|
59
|
+
"position": 4,
|
60
|
+
"human_type": "video_lesson",
|
61
|
+
"poster": "http://netology.ru/content/p3/3375@2x.png",
|
62
|
+
"video_url": "/courses/931/lessons/2271/iframe"
|
63
|
+
},
|
64
|
+
{
|
65
|
+
"id": 2272,
|
66
|
+
"name": "Составление объявлений и работа со ссылками",
|
67
|
+
"position": 5,
|
68
|
+
"human_type": "video_lesson",
|
69
|
+
"poster": "http://netology.ru/content/p3/3375@2x.png",
|
70
|
+
"video_url": "/courses/931/lessons/2272/iframe"
|
71
|
+
},
|
72
|
+
{
|
73
|
+
"id": 2273,
|
74
|
+
"name": "Загрузка кампании через редактор AdWords",
|
75
|
+
"position": 6,
|
76
|
+
"human_type": "video_lesson",
|
77
|
+
"poster": "http://netology.ru/content/p3/3375@2x.png",
|
78
|
+
"video_url": "/courses/931/lessons/2273/iframe"
|
79
|
+
},
|
80
|
+
{
|
81
|
+
"id": 2274,
|
82
|
+
"name": "Расширения объявлений",
|
83
|
+
"position": 7,
|
84
|
+
"human_type": "video_lesson",
|
85
|
+
"poster": "http://netology.ru/content/p3/3375@2x.png",
|
86
|
+
"video_url": "/courses/931/lessons/2274/iframe"
|
87
|
+
},
|
88
|
+
{
|
89
|
+
"id": 2275,
|
90
|
+
"name": "Эффективная кампания для КМС",
|
91
|
+
"position": 8,
|
92
|
+
"human_type": "video_lesson",
|
93
|
+
"poster": "http://netology.ru/content/p3/3375@2x.png",
|
94
|
+
"video_url": "/courses/931/lessons/2275/iframe"
|
95
|
+
},
|
96
|
+
{
|
97
|
+
"id": 2276,
|
98
|
+
"name": "Ремаркетинг в КМС",
|
99
|
+
"position": 9,
|
100
|
+
"human_type": "video_lesson",
|
101
|
+
"poster": "http://netology.ru/content/p3/3375@2x.png",
|
102
|
+
"video_url": "/courses/931/lessons/2276/iframe"
|
103
|
+
},
|
104
|
+
{
|
105
|
+
"id": 2277,
|
106
|
+
"name": "Подведение итогов курса",
|
107
|
+
"position": 10,
|
108
|
+
"human_type": "video_lesson",
|
109
|
+
"poster": "http://netology.ru/content/p3/3375@2x.png",
|
110
|
+
"video_url": "/courses/931/lessons/2277/iframe"
|
111
|
+
},
|
112
|
+
{
|
113
|
+
"id": 2547,
|
114
|
+
"name": "Контекстная реклама в Google AdWords: итоговый тест",
|
115
|
+
"position": 11,
|
116
|
+
"human_type": "test_final",
|
117
|
+
"questions": [
|
118
|
+
{
|
119
|
+
"id": "65953",
|
120
|
+
"position": 1,
|
121
|
+
"content": "При определении позиции объявления в блоке Google AdWords учитывается:",
|
122
|
+
"type": "radio",
|
123
|
+
"answers": [
|
124
|
+
{
|
125
|
+
"id": 1,
|
126
|
+
"content": "только ставка рекламодателя",
|
127
|
+
"is_correct": false
|
128
|
+
},
|
129
|
+
{
|
130
|
+
"id": 2,
|
131
|
+
"content": "ставка рекламодателя и прогнозируемый CTR объявления",
|
132
|
+
"is_correct": false
|
133
|
+
},
|
134
|
+
{
|
135
|
+
"id": 3,
|
136
|
+
"content": "показатель качества объявления и ставка рекламодателя",
|
137
|
+
"is_correct": true
|
138
|
+
}
|
139
|
+
]
|
140
|
+
},
|
141
|
+
{
|
142
|
+
"id": "65954",
|
143
|
+
"position": 2,
|
144
|
+
"content": "Вы предоставляете услуги выездной техпомощи на дорогах. Как лучше настроить ставки в объявлениях на мобильных устройствах?",
|
145
|
+
"type": "radio",
|
146
|
+
"answers": [
|
147
|
+
{
|
148
|
+
"id": 1,
|
149
|
+
"content": "снизить на 50%",
|
150
|
+
"is_correct": false
|
151
|
+
},
|
152
|
+
{
|
153
|
+
"id": 2,
|
154
|
+
"content": "повысить на 50−100%",
|
155
|
+
"is_correct": true
|
156
|
+
},
|
157
|
+
{
|
158
|
+
"id": 3,
|
159
|
+
"content": "снизить на 20%",
|
160
|
+
"is_correct": false
|
161
|
+
}
|
162
|
+
]
|
163
|
+
},
|
164
|
+
{
|
165
|
+
"id": "65955",
|
166
|
+
"position": 3,
|
167
|
+
"content": "Как лучше всего настроить геотаргетинг?",
|
168
|
+
"type": "radio",
|
169
|
+
"answers": [
|
170
|
+
{
|
171
|
+
"id": 1,
|
172
|
+
"content": "установить в настройках кампании таргетинг на Москву, затем по очереди добавить все остальные города",
|
173
|
+
"is_correct": false
|
174
|
+
},
|
175
|
+
{
|
176
|
+
"id": 2,
|
177
|
+
"content": "настроить на каждый город отдельную кампанию, где название города будет присутствовать в текстах объявлений",
|
178
|
+
"is_correct": true
|
179
|
+
},
|
180
|
+
{
|
181
|
+
"id": 3,
|
182
|
+
"content": "выбрать в настройках местоположений вариант «Дополнительно» и отметить все нужные точки на карте",
|
183
|
+
"is_correct": false
|
184
|
+
}
|
185
|
+
]
|
186
|
+
},
|
187
|
+
{
|
188
|
+
"id": "65956",
|
189
|
+
"position": 4,
|
190
|
+
"content": "Дневной бюджет в кампании можно задать:",
|
191
|
+
"type": "radio",
|
192
|
+
"answers": [
|
193
|
+
{
|
194
|
+
"id": 1,
|
195
|
+
"content": "на уровне аккаунта",
|
196
|
+
"is_correct": false
|
197
|
+
},
|
198
|
+
{
|
199
|
+
"id": 2,
|
200
|
+
"content": "на уровне рекламной кампании",
|
201
|
+
"is_correct": true
|
202
|
+
},
|
203
|
+
{
|
204
|
+
"id": 3,
|
205
|
+
"content": "на уровне группы объявлений",
|
206
|
+
"is_correct": false
|
207
|
+
}
|
208
|
+
]
|
209
|
+
},
|
210
|
+
{
|
211
|
+
"id": "65957",
|
212
|
+
"position": 5,
|
213
|
+
"content": "По какому запросу пользователя не будет показов?",
|
214
|
+
"type": "radio",
|
215
|
+
"answers": [
|
216
|
+
{
|
217
|
+
"id": 1,
|
218
|
+
"content": "купить мультиварку в интернет-магазине",
|
219
|
+
"is_correct": false
|
220
|
+
},
|
221
|
+
{
|
222
|
+
"id": 2,
|
223
|
+
"content": "где купить мультиварку в Самаре",
|
224
|
+
"is_correct": false
|
225
|
+
},
|
226
|
+
{
|
227
|
+
"id": 3,
|
228
|
+
"content": "мультиварки со скидкой в Москве",
|
229
|
+
"is_correct": true
|
230
|
+
}
|
231
|
+
]
|
232
|
+
},
|
233
|
+
{
|
234
|
+
"id": "65958",
|
235
|
+
"position": 6,
|
236
|
+
"content": "Как добавить словосочетание «своими руками» в качестве минус-слова на уровне кампании в Google AdWord?",
|
237
|
+
"type": "radio",
|
238
|
+
"answers": [
|
239
|
+
{
|
240
|
+
"id": 1,
|
241
|
+
"content": "на вкладке «Ключевые слова» добавить в минус-слова [своими руками]",
|
242
|
+
"is_correct": true
|
243
|
+
},
|
244
|
+
{
|
245
|
+
"id": 2,
|
246
|
+
"content": "на вкладке «Ключевые слова» добавить в минус-слова «свой» и «рука»",
|
247
|
+
"is_correct": false
|
248
|
+
},
|
249
|
+
{
|
250
|
+
"id": 3,
|
251
|
+
"content": "в настройках кампании вписать словосочетание «своими руками» в раздел «минус-слова на кампанию»",
|
252
|
+
"is_correct": false
|
253
|
+
}
|
254
|
+
]
|
255
|
+
},
|
256
|
+
{
|
257
|
+
"id": "65959",
|
258
|
+
"position": 7,
|
259
|
+
"content": "Группы объявлений помогают:",
|
260
|
+
"type": "radio",
|
261
|
+
"answers": [
|
262
|
+
{
|
263
|
+
"id": 1,
|
264
|
+
"content": "сгруппировать в кампании объявления по тематикам",
|
265
|
+
"is_correct": false
|
266
|
+
},
|
267
|
+
{
|
268
|
+
"id": 2,
|
269
|
+
"content": "выявить наиболее эффективные тексты объявлений",
|
270
|
+
"is_correct": true
|
271
|
+
},
|
272
|
+
{
|
273
|
+
"id": 3,
|
274
|
+
"content": "выявить новые ключевые слова для кампании",
|
275
|
+
"is_correct": false
|
276
|
+
}
|
277
|
+
]
|
278
|
+
},
|
279
|
+
{
|
280
|
+
"id": "65960",
|
281
|
+
"position": 8,
|
282
|
+
"content": "Как будет выглядеть заголовок объявления?",
|
283
|
+
"type": "radio",
|
284
|
+
"answers": [
|
285
|
+
{
|
286
|
+
"id": 1,
|
287
|
+
"content": "скидки на карнавальные костюмы",
|
288
|
+
"is_correct": false
|
289
|
+
},
|
290
|
+
{
|
291
|
+
"id": 2,
|
292
|
+
"content": "скидки на костюмы для детского сада",
|
293
|
+
"is_correct": false
|
294
|
+
},
|
295
|
+
{
|
296
|
+
"id": 3,
|
297
|
+
"content": "скидки на костюмы",
|
298
|
+
"is_correct": true
|
299
|
+
}
|
300
|
+
]
|
301
|
+
},
|
302
|
+
{
|
303
|
+
"id": "65961",
|
304
|
+
"position": 9,
|
305
|
+
"content": "Как будут отбираться дополнительные ссылки для каждого объявления?",
|
306
|
+
"type": "radio",
|
307
|
+
"answers": [
|
308
|
+
{
|
309
|
+
"id": 1,
|
310
|
+
"content": "нужно отметить приоритетные ссылки в каждой группе объявлений, тогда система покажет именно их",
|
311
|
+
"is_correct": false
|
312
|
+
},
|
313
|
+
{
|
314
|
+
"id": 2,
|
315
|
+
"content": "система выберет для показа те ссылки, которые будут наиболее релевантны конкретному объявлению",
|
316
|
+
"is_correct": true
|
317
|
+
},
|
318
|
+
{
|
319
|
+
"id": 3,
|
320
|
+
"content": "все ссылки будут показываться поочередно",
|
321
|
+
"is_correct": false
|
322
|
+
}
|
323
|
+
]
|
324
|
+
},
|
325
|
+
{
|
326
|
+
"id": "65962",
|
327
|
+
"position": 10,
|
328
|
+
"content": "Какие размеры баннеров лучше использовать для показов на сайтах КМС Google AdWords?",
|
329
|
+
"type": "radio",
|
330
|
+
"answers": [
|
331
|
+
{
|
332
|
+
"id": 1,
|
333
|
+
"content": "240x400, 250x250, 300x250, 970x250",
|
334
|
+
"is_correct": false
|
335
|
+
},
|
336
|
+
{
|
337
|
+
"id": 2,
|
338
|
+
"content": "300x250, 300x600, 970x250, 970x90",
|
339
|
+
"is_correct": false
|
340
|
+
},
|
341
|
+
{
|
342
|
+
"id": 3,
|
343
|
+
"content": "все допустимые размеры",
|
344
|
+
"is_correct": true
|
345
|
+
}
|
346
|
+
]
|
347
|
+
},
|
348
|
+
{
|
349
|
+
"id": "65963",
|
350
|
+
"position": 11,
|
351
|
+
"content": "Как лучше настроить ремаркетинг?",
|
352
|
+
"type": "radio",
|
353
|
+
"answers": [
|
354
|
+
{
|
355
|
+
"id": 1,
|
356
|
+
"content": "показывать объявления ремаркетинга всем посетителям сайта",
|
357
|
+
"is_correct": false
|
358
|
+
},
|
359
|
+
{
|
360
|
+
"id": 2,
|
361
|
+
"content": "создать список ремаркетинга для тех пользователей, которые смотрели страницу «Все услуги», но не переходили на страницу нужной услуги",
|
362
|
+
"is_correct": false
|
363
|
+
},
|
364
|
+
{
|
365
|
+
"id": 3,
|
366
|
+
"content": "создать отдельный список ремаркетинга для тех, кто посещал страницу с нужной услугой, и предлагать им специальные условия по данной услуге, которые описаны на отдельной странице сайта",
|
367
|
+
"is_correct": true
|
368
|
+
}
|
369
|
+
]
|
370
|
+
},
|
371
|
+
{
|
372
|
+
"id": "65964",
|
373
|
+
"position": 12,
|
374
|
+
"content": "Откуда Google AdWords берет данные для разделения аудитории по полу, возрасту и родительскому статусу?",
|
375
|
+
"type": "radio",
|
376
|
+
"answers": [
|
377
|
+
{
|
378
|
+
"id": 1,
|
379
|
+
"content": "выводы делаются на основе посещенных пользователем сайтов",
|
380
|
+
"is_correct": false
|
381
|
+
},
|
382
|
+
{
|
383
|
+
"id": 2,
|
384
|
+
"content": "из профилей пользователей в социальных сетях",
|
385
|
+
"is_correct": false
|
386
|
+
},
|
387
|
+
{
|
388
|
+
"id": 3,
|
389
|
+
"content": "из регистрационных данных в сервисах Google",
|
390
|
+
"is_correct": true
|
391
|
+
}
|
392
|
+
]
|
393
|
+
},
|
394
|
+
{
|
395
|
+
"id": "65965",
|
396
|
+
"position": 13,
|
397
|
+
"content": "Как лучше настроить демографические данные для показов объявлений в КМС?",
|
398
|
+
"type": "radio",
|
399
|
+
"answers": [
|
400
|
+
{
|
401
|
+
"id": 1,
|
402
|
+
"content": "включить показы женщинам от 25 до 34 лет, все остальные половые и возрастные категории отключить ",
|
403
|
+
"is_correct": false
|
404
|
+
},
|
405
|
+
{
|
406
|
+
"id": 2,
|
407
|
+
"content": "на вкладке «Пол» оставить включенными категории «Женщины» и «Неизвестно», на вкладке «Возраст» — «25−34» и «Неизвестно»",
|
408
|
+
"is_correct": true
|
409
|
+
},
|
410
|
+
{
|
411
|
+
"id": 3,
|
412
|
+
"content": "включить показы женщинам от 18 до 45 лет, чтобы немного расширить аудиторию",
|
413
|
+
"is_correct": false
|
414
|
+
}
|
415
|
+
]
|
416
|
+
}
|
417
|
+
]
|
418
|
+
}
|
419
|
+
]
|
420
|
+
}
|
421
|
+
]
|
422
|
+
}
|
@@ -0,0 +1,57 @@
|
|
1
|
+
[
|
2
|
+
{
|
3
|
+
"id": "426",
|
4
|
+
"name": "Основы email-маркетинга",
|
5
|
+
"last_updated_at": "1387176082"
|
6
|
+
},
|
7
|
+
{
|
8
|
+
"id": "429",
|
9
|
+
"name": "Основы контекстной рекламы",
|
10
|
+
"last_updated_at": "1386236837"
|
11
|
+
},
|
12
|
+
{
|
13
|
+
"id": "430",
|
14
|
+
"name": "Юзабилити в электронной коммерции",
|
15
|
+
"last_updated_at": "1387176130"
|
16
|
+
},
|
17
|
+
{
|
18
|
+
"id": "431",
|
19
|
+
"name": "Как писать продающие тексты: свод правил для копирайтера",
|
20
|
+
"last_updated_at": "1387176102"
|
21
|
+
},
|
22
|
+
{
|
23
|
+
"id": "432",
|
24
|
+
"name": "Баннерная реклама и RTB-технологии",
|
25
|
+
"last_updated_at": "1387105068"
|
26
|
+
},
|
27
|
+
{
|
28
|
+
"id": "433",
|
29
|
+
"name": "Реклама и маркетинг в интернет-среде",
|
30
|
+
"last_updated_at": "1387176111"
|
31
|
+
},
|
32
|
+
{
|
33
|
+
"id": "435",
|
34
|
+
"name": "Основы веб-аналитики",
|
35
|
+
"last_updated_at": "1387176094"
|
36
|
+
},
|
37
|
+
{
|
38
|
+
"id": "436",
|
39
|
+
"name": "Конкурентный анализ и исследование целевой аудитории",
|
40
|
+
"last_updated_at": "1387176072"
|
41
|
+
},
|
42
|
+
{
|
43
|
+
"id": "439",
|
44
|
+
"name": "Основы маркетинга в социальных сетях (SMM)",
|
45
|
+
"last_updated_at": "1385968453"
|
46
|
+
},
|
47
|
+
{
|
48
|
+
"id": "441",
|
49
|
+
"name": "Продающие тексты для интернет-магазинов",
|
50
|
+
"last_updated_at": "1385968536"
|
51
|
+
},
|
52
|
+
{
|
53
|
+
"id": "444",
|
54
|
+
"name": "Запись и распространение аудиоподкастов",
|
55
|
+
"last_updated_at": "1386846925"
|
56
|
+
}
|
57
|
+
]
|
@@ -0,0 +1,79 @@
|
|
1
|
+
module NetologiestWebMock
|
2
|
+
def mock_api
|
3
|
+
auth_url = "#{Netologiest.config.api_url}/gettoken"
|
4
|
+
url = "#{auth_url}?client_secret=#{Netologiest.config.api_key}"
|
5
|
+
authorize_stub(url)
|
6
|
+
end
|
7
|
+
|
8
|
+
def authorize_stub(url)
|
9
|
+
stub_request(:get, url)
|
10
|
+
.to_return(
|
11
|
+
headers: { 'Content-Type' => 'application/json' },
|
12
|
+
status: 200,
|
13
|
+
body: File.read("spec/fixtures/auth.json")
|
14
|
+
)
|
15
|
+
end
|
16
|
+
|
17
|
+
def courses_stub
|
18
|
+
token = "S3PBVG38O1209Y01X5LEK0PYH0MT3YDZ"
|
19
|
+
url = Netologiest.config.api_url + "/courses?token=#{token}"
|
20
|
+
stub_request(:get, url).to_return(
|
21
|
+
headers: { 'Content-Type' => 'application/json' },
|
22
|
+
status: 200,
|
23
|
+
body: File.read("spec/fixtures/courses.json")
|
24
|
+
)
|
25
|
+
end
|
26
|
+
|
27
|
+
def empty_course_stub(id)
|
28
|
+
token = "S3PBVG38O1209Y01X5LEK0PYH0MT3YDZ"
|
29
|
+
url = Netologiest.config.api_url + "/courses/#{id}?token=#{token}"
|
30
|
+
stub_request(:get, url).to_return(
|
31
|
+
headers: { 'Content-Type' => 'application/json' },
|
32
|
+
status: 200,
|
33
|
+
body: "[]"
|
34
|
+
)
|
35
|
+
end
|
36
|
+
|
37
|
+
def detail_course_stub(id)
|
38
|
+
token = "S3PBVG38O1209Y01X5LEK0PYH0MT3YDZ"
|
39
|
+
url = Netologiest.config.api_url + "/courses/#{id}?token=#{token}"
|
40
|
+
stub_request(:get, url).to_return(
|
41
|
+
headers: { 'Content-Type' => 'application/json' },
|
42
|
+
status: 200,
|
43
|
+
body: File.read("spec/fixtures/course.json")
|
44
|
+
)
|
45
|
+
end
|
46
|
+
|
47
|
+
def bad_token_stub(id)
|
48
|
+
url = Netologiest.config.api_url + "/courses/#{id}?token=S3PBVG38O1209Y01X5LEK0PYH0MT3YDZ"
|
49
|
+
stub_request(:get, url).to_return(
|
50
|
+
headers: { 'Content-Type' => 'application/json' },
|
51
|
+
status: 401,
|
52
|
+
body: "Need token"
|
53
|
+
)
|
54
|
+
end
|
55
|
+
|
56
|
+
def auth_failed_stub(url)
|
57
|
+
stub_request(:get, url)
|
58
|
+
.to_return(
|
59
|
+
headers: { 'Content-Type' => 'application/json' },
|
60
|
+
status: 401,
|
61
|
+
body: "Need correct client_secret"
|
62
|
+
)
|
63
|
+
end
|
64
|
+
|
65
|
+
def many_requests_stub(id)
|
66
|
+
token = "S3PBVG38O1209Y01X5LEK0PYH0MT3YDZ"
|
67
|
+
url = Netologiest.config.api_url + "/courses/#{id}?token=#{token}"
|
68
|
+
stub_request(:get, url).to_return(
|
69
|
+
{
|
70
|
+
headers: { 'Content-Type' => 'application/json' },
|
71
|
+
status: 401,
|
72
|
+
body: "Need token"
|
73
|
+
},
|
74
|
+
headers: { 'Content-Type' => 'application/json' },
|
75
|
+
status: 200,
|
76
|
+
body: File.read("spec/fixtures/course.json")
|
77
|
+
)
|
78
|
+
end
|
79
|
+
end
|
@@ -0,0 +1,11 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
|
3
|
+
describe Netologiest::Resource do
|
4
|
+
before(:each) { mock_api }
|
5
|
+
|
6
|
+
it "get authorization token automaticaly" do
|
7
|
+
res = described_class.new
|
8
|
+
expect(res.token).to eq "S3PBVG38O1209Y01X5LEK0PYH0MT3YDZ"
|
9
|
+
expect(res.token_expire.present?).to be_truthy
|
10
|
+
end
|
11
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,16 @@
|
|
1
|
+
require 'rspec'
|
2
|
+
require 'netologiest'
|
3
|
+
require 'webmock/rspec'
|
4
|
+
require 'helpers/webmock_helpers'
|
5
|
+
|
6
|
+
begin
|
7
|
+
require "pry-byebug"
|
8
|
+
rescue LoadError
|
9
|
+
end
|
10
|
+
|
11
|
+
ENV["NETOLOGIEST_CONF"] = File.expand_path("../fixtures/netologiest_test.yml", __FILE__)
|
12
|
+
|
13
|
+
RSpec.configure do |config|
|
14
|
+
config.mock_with :rspec
|
15
|
+
include NetologiestWebMock
|
16
|
+
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: netologiest
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Vlad Dem
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2015-07-
|
11
|
+
date: 2015-07-30 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|
@@ -101,7 +101,21 @@ dependencies:
|
|
101
101
|
- !ruby/object:Gem::Version
|
102
102
|
version: '0.3'
|
103
103
|
- !ruby/object:Gem::Dependency
|
104
|
-
name:
|
104
|
+
name: rest-client
|
105
|
+
requirement: !ruby/object:Gem::Requirement
|
106
|
+
requirements:
|
107
|
+
- - ">="
|
108
|
+
- !ruby/object:Gem::Version
|
109
|
+
version: '0'
|
110
|
+
type: :runtime
|
111
|
+
prerelease: false
|
112
|
+
version_requirements: !ruby/object:Gem::Requirement
|
113
|
+
requirements:
|
114
|
+
- - ">="
|
115
|
+
- !ruby/object:Gem::Version
|
116
|
+
version: '0'
|
117
|
+
- !ruby/object:Gem::Dependency
|
118
|
+
name: activesupport
|
105
119
|
requirement: !ruby/object:Gem::Requirement
|
106
120
|
requirements:
|
107
121
|
- - ">="
|
@@ -143,8 +157,20 @@ files:
|
|
143
157
|
- README.md
|
144
158
|
- Rakefile
|
145
159
|
- lib/netologiest.rb
|
160
|
+
- lib/netologiest/config.rb
|
161
|
+
- lib/netologiest/exceptions.rb
|
162
|
+
- lib/netologiest/resource.rb
|
163
|
+
- lib/netologiest/resources/course.rb
|
146
164
|
- lib/netologiest/version.rb
|
147
165
|
- netologiest.gemspec
|
166
|
+
- spec/course_spec.rb
|
167
|
+
- spec/fixtures/auth.json
|
168
|
+
- spec/fixtures/course.json
|
169
|
+
- spec/fixtures/courses.json
|
170
|
+
- spec/fixtures/netologiest_test.yml
|
171
|
+
- spec/helpers/webmock_helpers.rb
|
172
|
+
- spec/resource_spec.rb
|
173
|
+
- spec/spec_helper.rb
|
148
174
|
homepage: ''
|
149
175
|
licenses:
|
150
176
|
- MIT
|
@@ -169,5 +195,13 @@ rubygems_version: 2.4.5
|
|
169
195
|
signing_key:
|
170
196
|
specification_version: 4
|
171
197
|
summary: Ruby API client for Netology
|
172
|
-
test_files:
|
198
|
+
test_files:
|
199
|
+
- spec/course_spec.rb
|
200
|
+
- spec/fixtures/auth.json
|
201
|
+
- spec/fixtures/course.json
|
202
|
+
- spec/fixtures/courses.json
|
203
|
+
- spec/fixtures/netologiest_test.yml
|
204
|
+
- spec/helpers/webmock_helpers.rb
|
205
|
+
- spec/resource_spec.rb
|
206
|
+
- spec/spec_helper.rb
|
173
207
|
has_rdoc:
|