diff --git a/lib/apiwrapper.rb b/lib/apiwrapper.rb new file mode 100644 index 00000000..3f5b55b4 --- /dev/null +++ b/lib/apiwrapper.rb @@ -0,0 +1,53 @@ +require "HTTParty" +require "dotenv" +Dotenv.load + +module Slack + module ApiWrapper + class SlackError < StandardError; end + + URL_BASE = "https://slack.com/api/" + + def self.get_channels + url_tail = "channels.list" + response = get_json(url_tail: url_tail) + return response + end + + def self.get_users + url_tail = "users.list" + response = get_json(url_tail: url_tail) + return response + end + + def self.get_json(url_tail:) + token = ENV["SLACK_API_KEY"] + query_params = { + "token": token, + } + response = HTTParty.get("#{URL_BASE}#{url_tail}", query: query_params) + if !response["ok"] + raise SlackError.new + end + return response + end + + def self.post(text:, recipient:) + token = ENV["SLACK_API_KEY"] + response = HTTParty.post( + "#{URL_BASE}chat.postMessage", + headers: { "Content-Type" => "application/x-www-form-urlencoded" }, + body: { + token: token, + channel: recipient, + text: text, + }, + ) + if response["ok"] + return true + else + raise ApiWrapper::SlackError, "Error when posting #{text} to #{recipient}, error: #{response.parsed_response["error"]}" + end + end + end +end diff --git a/lib/channel.rb b/lib/channel.rb new file mode 100644 index 00000000..737c0cb3 --- /dev/null +++ b/lib/channel.rb @@ -0,0 +1,33 @@ +require_relative "recipient" +require_relative "apiwrapper" + +module Slack + class Channel < Recipient + attr_reader :member_count, :topic + + def initialize(id:, name:, member_count:, topic:) + super(id: id, name: name) + @member_count = member_count + @topic = topic + end + + def details + return "Name: #{name}, \nID: #{id}, \nTopic: #{topic}, \nMember Count: #{member_count}" + end + + def self.create_channels + channels = [] + begin + response = ApiWrapper::get_channels + response["channels"].each do |channel| + channels << self.new(id: channel["id"], + name: channel["name"], + member_count: channel["num_members"], + topic: channel["topic"]["value"]) + end + rescue ApiWrapper::SlackError + end + return channels + end + end +end diff --git a/lib/recipient.rb b/lib/recipient.rb new file mode 100644 index 00000000..e008a7f6 --- /dev/null +++ b/lib/recipient.rb @@ -0,0 +1,14 @@ +module Slack + class Recipient + attr_reader :name, :id + + def initialize(name:, id:) + @name = name + @id = id + end + + def details + raise NotImplementedError.new + end + end +end diff --git a/lib/slack.rb b/lib/slack.rb index 960cf2f7..9bd7949c 100755 --- a/lib/slack.rb +++ b/lib/slack.rb @@ -1,11 +1,106 @@ #!/usr/bin/env ruby +require_relative "workspace" def main - puts "Welcome to the Ada Slack CLI!" + workspace = Slack::Workspace.new + puts "\nWelcome to the Ada Slack CLI!" + puts "-------------------------------" + puts "\nWorkspace: Sav-Elise-API Project" + puts "Number of users: #{workspace.users.length}" + puts "Number of channels: #{workspace.channels.length}\n\n" + options = ["List Users", "List Channels", "Select User", "Select Channel", "Details", "Send Message", "Quit"] + while true + puts "--------OPTIONS--------" + puts options + print "\nPlease enter your command: " + input = gets.chomp.upcase + case input + when "LIST USERS" + list_users workspace + when "LIST CHANNELS" + list_channels workspace + when "SELECT USER" + select_user workspace + when "SELECT CHANNEL" + select_channel workspace + when "SEND MESSAGE" + send_message workspace + when "DETAILS" + details workspace + when "QUIT" + break + else + puts "Command not recognized. Try again!\n\n" + end + end + puts "Thank you for using the Ada Slack CLI" +end + +def list_users(workspace) + list = workspace.list_all(list: workspace.users) + if list.empty? + puts "Unable to access users at this time." + else + puts "\n" + list + "\n" + end +end - # TODO project +def list_channels(workspace) + list = workspace.list_all(list: workspace.channels) + if list.empty? + puts "Unable to access channels at this time." + else + puts "\n" + list + "\n" + end +end - puts "Thank you for using the Ada Slack CLI" +def select_user(workspace) + print "Please enter user's name, real name or id: " + input = gets.chomp.upcase + user = workspace.find_user(input: input) + if user + workspace.select_recipient(recipient: user) + puts "Successfully upated selected user to #{user.name}.\n\n" + else + puts "#{input.capitalize} is not a valid user.\n\n" + end +end + +def select_channel(workspace) + print "Please enter channel's name or id: " + input = gets.chomp.upcase + channel = workspace.find_channel(input: input) + if channel + workspace.select_recipient(recipient: channel) + puts "Successfully upated selected channel to #{channel.name}.\n\n" + else + puts "#{input.capitalize} is not a valid channel.\n\n" + end +end + +def send_message(workspace) + recipient = workspace.selected + if recipient + puts "Please enter the message you want to send:" + message = gets.chomp + if message.empty? + puts "Error: No message to send.\n\n" + else + workspace.send_message(text: message) + puts "Message was sent to #{recipient.name}.\n\n" + end + else + puts "Error: first select channel or user.\n\n" + end +end + +def details(workspace) + details = workspace.details_for_selected + if details.empty? + puts "Error: first select channel or user.\n\n" + else + puts "\n" + details + "\n\n" + end end -main if __FILE__ == $PROGRAM_NAME \ No newline at end of file +main if __FILE__ == $PROGRAM_NAME diff --git a/lib/user.rb b/lib/user.rb new file mode 100644 index 00000000..571df6ad --- /dev/null +++ b/lib/user.rb @@ -0,0 +1,31 @@ +require_relative "recipient" +require_relative "apiwrapper" + +module Slack + class User < Recipient + attr_reader :real_name + + def initialize(id:, name:, real_name:) + super(id: id, name: name) + @real_name = real_name + end + + def details + return "Name: #{name}, \nReal Name: #{real_name}, \nID: #{id}" + end + + def self.create_users + users = [] + begin + response = ApiWrapper::get_users + response["members"].each do |user| + users << self.new(id: user["id"], + name: user["name"], + real_name: user["real_name"]) + end + rescue ApiWrapper::SlackError + end + return users + end + end +end diff --git a/lib/workspace.rb b/lib/workspace.rb new file mode 100644 index 00000000..47daf1d7 --- /dev/null +++ b/lib/workspace.rb @@ -0,0 +1,61 @@ +require_relative "channel" +require_relative "user" + +module Slack + class Workspace + attr_reader :users, :channels, :selected + + def initialize + @users = User.create_users + @channels = Channel.create_channels + @selected = nil + end + + def select_recipient(recipient:) + @selected = recipient + return true + end + + def list_all(list:) + result = "" + list.each do |elem| + result << elem.details + result << "\n-----------------------\n" + end + return result + end + + def send_message(text:) + return false unless selected + begin + ApiWrapper.post(text: text, recipient: selected.id) + rescue ApiWrapper::SlackError + return false + end + return true + end + + def details_for_selected + return "" unless selected + return selected.details + end + + def find_user(input:) + users.each do |user| + if input == user.name.upcase || input == user.real_name.upcase || input == user.id.upcase + return user + end + end + return nil + end + + def find_channel(input:) + channels.each do |channel| + if input == channel.name.upcase || input == channel.id.upcase + return channel + end + end + return nil + end + end +end diff --git a/specs/apiwrapper_spec.rb b/specs/apiwrapper_spec.rb new file mode 100644 index 00000000..aec88e42 --- /dev/null +++ b/specs/apiwrapper_spec.rb @@ -0,0 +1,84 @@ +require_relative "test_helper" + +describe "Api Wrapper module" do + describe "Api Wrapper module" do + it "responds to get_users and get_channels" do + expect(Slack::ApiWrapper).must_respond_to :get_users + expect(Slack::ApiWrapper).must_respond_to :get_channels + end + end + + describe "ApiWrapper.get_json" do + it "successfully gets a json object" do + url_tail = "users.list" + VCR.use_cassette("slack_api") do + response = Slack::ApiWrapper.get_json(url_tail: url_tail) + expect(response).must_be_instance_of HTTParty::Response + end + end + + it "raise an error if an invalid url is used" do + url_tail = "abc" + VCR.use_cassette("slack_api") do + expect { + Slack::ApiWrapper.get_json(url_tail: url_tail) + }.must_raise Slack::ApiWrapper::SlackError + end + end + + it "raise an error when an invalid token is used" do + VCR.use_cassette("slack_api") do + real_token = ENV["SLACK_API_KEY"] + ENV["SLACK_API_KEY"] = "NOT_REAL_TOKEN" + expect { + Slack::ApiWrapper.post(text: "Test message with invalid key", recipient: "general") + }.must_raise Slack::ApiWrapper::SlackError + ENV["SLACK_API_KEY"] = real_token + end + end + end + + describe "ApiWrapper.get_channels" do + it "will return a Response object" do + VCR.use_cassette("slack_api") do + response = Slack::ApiWrapper.get_channels + expect(response).must_be_instance_of HTTParty::Response + end + end + end + + describe "ApiWrapper.post" do + it "can send a valid message to a channel" do + VCR.use_cassette("slack_api") do + response = Slack::ApiWrapper.post(text: "test text", recipient: "random") + expect(response).must_equal true + end + end + + it "can send a valid message to a user" do + VCR.use_cassette("slack_api") do + user_id = "UH2RH81RA" + response = Slack::ApiWrapper.post(text: "test text", recipient: user_id) + expect(response).must_equal true + end + end + + it "will raise exception if invalid channel used" do + VCR.use_cassette("slack_api") do + user_id = "Noarealid234" + expect { + Slack::ApiWrapper.post(text: "test text", recipient: user_id) + }.must_raise Slack::ApiWrapper::SlackError + end + end + + it "will raise exception if no text is provided" do + VCR.use_cassette("slack_api") do + user_id = "UH2RH81RA" + expect { + Slack::ApiWrapper.post(text: "", recipient: user_id) + }.must_raise Slack::ApiWrapper::SlackError + end + end + end +end diff --git a/specs/channel_spec.rb b/specs/channel_spec.rb new file mode 100644 index 00000000..28363f9c --- /dev/null +++ b/specs/channel_spec.rb @@ -0,0 +1,37 @@ +require_relative "test_helper.rb" + +describe "Channel class" do + let(:channel) { + Slack::Channel.new(id: "CBD", name: "general", topic: "stuff", member_count: "4") + } + + describe "Channel#initialize" do + it "returns an instance of Channel and a kind of Recipient" do + expect(channel).must_be_instance_of Slack::Channel + expect(channel).must_be_kind_of Slack::Recipient + end + + it "Checks instance variable name, id, topic, member_count: respond & equal to" do + expect(channel).must_respond_to :name + expect(channel).must_respond_to :id + expect(channel).must_respond_to :topic + expect(channel).must_respond_to :member_count + + expect(channel.name).must_equal "general" + expect(channel.id).must_equal "CBD" + expect(channel.topic).must_equal "stuff" + expect(channel.member_count).must_equal "4" + end + end + + describe "Channel#details" do + it "returns a String" do + expect(channel.details).must_be_instance_of String + end + + it "Checks details return the accurate String" do + string_format = "Name: #{channel.name}, \nID: #{channel.id}, \nTopic: #{channel.topic}, \nMember Count: #{channel.member_count}" + expect(channel.details).must_equal string_format + end + end +end diff --git a/specs/recipient_spec.rb b/specs/recipient_spec.rb new file mode 100644 index 00000000..403bcc79 --- /dev/null +++ b/specs/recipient_spec.rb @@ -0,0 +1,29 @@ +require_relative "test_helper.rb" + +describe "Recipient class" do + let(:r) { + Slack::Recipient.new(name: "test", id: "abc") + } + describe "initialize" do + it "must be a kind of Recipient" do + expect(r).must_be_instance_of Slack::Recipient + end + + it "will respond to instance variable, and equal correct value" do + expect(r).must_respond_to :name + expect(r).must_respond_to :id + expect(r.name).must_equal "test" + expect(r.id).must_equal "abc" + end + end + + # Check details raising NotImplemented Error when being called + + describe "details method" do + it "raises an error when being invoked by an instance of Recipient" do + expect { + r.details + }.must_raise NotImplementedError + end + end +end diff --git a/specs/test_helper.rb b/specs/test_helper.rb index 81ccd06b..c51934a3 100644 --- a/specs/test_helper.rb +++ b/specs/test_helper.rb @@ -1,15 +1,34 @@ -require 'simplecov' +require "simplecov" SimpleCov.start -require 'minitest' -require 'minitest/autorun' -require 'minitest/reporters' -require 'minitest/skip_dsl' -require 'vcr' +require "minitest" +require "minitest/autorun" +require "minitest/reporters" +require "minitest/skip_dsl" +require "webmock/minitest" +require "vcr" +require "dotenv" +require "HTTParty" + +Dotenv.load + +require_relative "../lib/recipient.rb" +require_relative "../lib/user.rb" +require_relative "../lib/channel" +require_relative "../lib/apiwrapper" +require_relative "../lib/workspace.rb" Minitest::Reporters.use! Minitest::Reporters::SpecReporter.new VCR.configure do |config| config.cassette_library_dir = "specs/cassettes" config.hook_into :webmock -end \ No newline at end of file + config.default_cassette_options = { + :record => :new_episodes, # record new data when we don't have it yet + :match_requests_on => [:method, :uri, :body], # The http method, URI and body of a request all need to match + } + + config.filter_sensitive_data("") do + ENV["SLACK_API_KEY"] + end +end diff --git a/specs/user_spec.rb b/specs/user_spec.rb new file mode 100644 index 00000000..30140d71 --- /dev/null +++ b/specs/user_spec.rb @@ -0,0 +1,84 @@ +require_relative "test_helper" + +describe "User class" do + let(:user) { + Slack::User.new(id: "123", name: "sam", real_name: "sam bones") + } + describe "User#initialize" do + it "Checks type is User and type Recipient" do + expect(user).must_be_instance_of Slack::User + expect(user).must_be_kind_of Slack::Recipient + end + + it "Checks instance variable name, real_name and id: respond & equal to" do + expect(user.name).must_equal "sam" + expect(user.real_name).must_equal "sam bones" + expect(user.id).must_equal "123" + end + end + + describe "User#details" do + it "returns a String" do + expect(user.details).must_be_instance_of String + end + + it "Checks details return the accurate String: " do + string_format = "Name: #{user.name}, \nReal Name: #{user.real_name}, \nID: #{user.id}" + expect(user.details).must_equal string_format + end + end + + # user 2 - "UH2RH81RA" + # "elise.pham88" + # "elise.pham88" + + # channel 2 = "CH2P3NB0T" + # "general" + # "Company-wide announcements and work-based matters" + # 2 + + describe "User.create_users" do + before do + VCR.use_cassette("slack_api") do + @users = Slack::User.create_users + end + end + + it "will return an array" do + expect(@users).must_be_instance_of Array + end + + it "returns correct User objects" do + expect(@users[1].name).must_equal "elise.pham88" + expect(@users[1].id).must_equal "UH2RH81RA" + expect(@users[1].real_name).must_equal "elise.pham88" + end + + it "returns correct number of users in workspace" do + expect(@users.length).must_equal 3 + end + end + + describe "Channel.create_channels" do + before do + VCR.use_cassette("slack_api") do + @channels = Slack::Channel.create_channels + end + end + + it "will return an array" do + expect(@channels).must_be_instance_of Array + end + + it "returns correct Channel objects" do + expect(@channels[1].name).must_equal "general" + expect(@channels[1].id).must_equal "CH2P3NB0T" + expect(@channels[1].topic).must_equal "Company-wide announcements and work-based matters" + expect(@channels[1].member_count).must_equal 2 + end + + it "returns correct number of channels in workspace" do + expect(@channels.length).must_equal 4 + end + end +end diff --git a/specs/workspace_spec.rb b/specs/workspace_spec.rb new file mode 100644 index 00000000..afd9fa49 --- /dev/null +++ b/specs/workspace_spec.rb @@ -0,0 +1,141 @@ +require_relative "test_helper" + +describe "Workspace class" do + before do + VCR.use_cassette("slack_api") do + @workspace = Slack::Workspace.new + end + end + + describe "Workspace#initialize" do + it "returns a workspace object" do + expect(@workspace).must_be_instance_of Slack::Workspace + end + + it "responds to instance variable and selected is nil" do + expect(@workspace).must_respond_to :channels + expect(@workspace).must_respond_to :users + expect(@workspace).must_respond_to :selected + expect(@workspace.selected).must_be_nil + end + + it "has correct instance variable types" do + expect(@workspace.channels[1]).must_be_instance_of Slack::Channel + expect(@workspace.users[1]).must_be_instance_of Slack::User + end + end + + describe "Workspace#Select_recipient" do + it "will set the selected variable to input channel and returns true" do + expect(@workspace.select_recipient(recipient: @workspace.channels[1])).must_equal true + expect(@workspace.selected).must_equal @workspace.channels[1] + end + + it "will set the selected variable to input user and returns true" do + @workspace.select_recipient(recipient: @workspace.users[1]) + expect(@workspace.selected).must_equal @workspace.users[1] + end + end + + describe "Workspace#list_all" do + it "will return a string" do + expect(@workspace.list_all(list: @workspace.channels)).must_be_instance_of String + expect(@workspace.list_all(list: @workspace.users)).must_be_instance_of String + end + + it "will return the correct String format" do + test_list = @workspace.list_all(list: @workspace.channels) + expected_result = "Name: slack-api-project, \nID: CH0EG6M0Q, \nTopic: , \nMember Count: 2\n" + + "-----------------------\n" + + "Name: general, \nID: CH2P3NB0T, \nTopic: Company-wide announcements and work-based matters, \nMember Count: 2\n" + + "-----------------------\n" + + "Name: random, \nID: CH2RH8AMA, \nTopic: Non-work banter and water cooler conversation, \nMember Count: 2\n" + + "-----------------------\n" + + "Name: super-awesome-stuffs, \nID: CH3UNG023, \nTopic: , \nMember Count: 2\n" + + "-----------------------\n" + expect(test_list).must_equal expected_result + end + + it "will return the correct String format" do + test_list = @workspace.list_all(list: @workspace.users) + expected_result = "Name: slackbot, \nReal Name: Slackbot, \nID: USLACKBOT\n" + + "-----------------------\n" + + "Name: elise.pham88, \nReal Name: elise.pham88, \nID: UH2RH81RA\n" + + "-----------------------\n" + + "Name: s.dippolito, \nReal Name: Sav Dippolito, \nID: UH3UN350X\n" + + "-----------------------\n" + expect(test_list).must_equal expected_result + end + end + + describe "Workspace#send_message" do + it "will return true if post succesful" do + VCR.use_cassette("slack_api") do + @workspace.select_recipient(recipient: @workspace.channels[1]) + expect(@workspace.send_message(text: "Something new to test!")).must_equal true + end + end + + it "will return false if post is unsuccessful" do + VCR.use_cassette("slack_api") do + expect(@workspace.send_message(text: "Something new to test!")).must_equal false + @workspace.select_recipient(recipient: @workspace.channels[1]) + expect(@workspace.send_message(text: "")).must_equal false + end + end + end + + describe "Workspace#details_for_selected" do + it "will return a String" do + expect(@workspace.details_for_selected).must_be_instance_of String + end + + it "will return string details of selected object if valid" do + @workspace.select_recipient(recipient: @workspace.channels[1]) + expect(@workspace.details_for_selected).must_equal "Name: general, \nID: CH2P3NB0T, \nTopic: Company-wide announcements and work-based matters, \nMember Count: 2" + end + + it "will return empty string if selected object is nil" do + expect(@workspace.details_for_selected).must_equal "" + end + end + + describe "Workspace#find_user" do + it "will return a user object, given a valid user's name, real_name or id" do + user_name = "elise.pham88" + real_name = "elise.pham88" + user_id = "UH2RH81RA" + user = @workspace.find_user(input: user_name.upcase) + expect(user).must_be_instance_of Slack::User + expect(user.name).must_equal "elise.pham88" + user = @workspace.find_user(input: real_name.upcase) + expect(user).must_be_instance_of Slack::User + expect(user.real_name).must_equal "elise.pham88" + user = @workspace.find_user(input: user_id.upcase) + expect(user).must_be_instance_of Slack::User + expect(user.id).must_equal "UH2RH81RA" + end + + it "will return nil if invalid users name, real_name or id" do + user_name = "elissdfsdfe.pham88" + real_name = "elisesdfsdf.pham88" + user_id = "UH2RHsdfsdf81RA" + expect(@workspace.find_user(input: user_name.upcase)).must_be_nil + expect(@workspace.find_user(input: real_name.upcase)).must_be_nil + expect(@workspace.find_user(input: user_id.upcase)).must_be_nil + end + end + + describe "Workspace#find_channel" do + it "will return a user object, given a valid channel's name or id" do + channel_name = "random" + channel_id = "CH2RH8AMA" + channel = @workspace.find_channel(input: channel_name.upcase) + expect(channel).must_be_instance_of Slack::Channel + expect(channel.name).must_equal "random" + channel = @workspace.find_channel(input: channel_id.upcase) + expect(channel).must_be_instance_of Slack::Channel + expect(channel.id).must_equal "CH2RH8AMA" + end + end +end