Skip to content
This repository has been archived by the owner on Nov 6, 2023. It is now read-only.

Publish to twitter after publishing a new video using Twitter4J implementation #19

Open
wants to merge 4 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions src/mooc/build.gradle
Original file line number Diff line number Diff line change
@@ -1,3 +1,11 @@
dependencies {
compile project(":src:shared")

compileOnly 'org.projectlombok:lombok:1.18.16'
annotationProcessor 'org.projectlombok:lombok:1.18.16'

testCompileOnly 'org.projectlombok:lombok:1.18.16'
testAnnotationProcessor 'org.projectlombok:lombok:1.18.16'

compile 'org.twitter4j:twitter4j-stream:4.0.6'
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package tv.codely.mooc.errors.application;

import lombok.AllArgsConstructor;
import tv.codely.mooc.errors.domain.ErrorGenerated;
import tv.codely.mooc.errors.domain.ErrorProcessor;
import tv.codely.shared.application.DomainEventSubscriber;

@AllArgsConstructor
public class ProcessErrorGenerated implements DomainEventSubscriber<ErrorGenerated> {

ErrorProcessor errorProcessor;

@Override
public Class<ErrorGenerated> subscribedTo() {
return ErrorGenerated.class;
}

@Override
public void consume(ErrorGenerated event) {
errorProcessor.processError(event);
}
}
18 changes: 18 additions & 0 deletions src/mooc/main/tv/codely/mooc/errors/domain/ErrorGenerated.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package tv.codely.mooc.errors.domain;

import lombok.Value;
import tv.codely.shared.domain.DomainEvent;

@Value
public class ErrorGenerated implements DomainEvent {

String errorMessage;
Throwable cause;

private static final String FULL_QUALIFIED_EVENT_NAME = "codelytv.errors.error.generated";

@Override
public String fullQualifiedEventName() {
return FULL_QUALIFIED_EVENT_NAME;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package tv.codely.mooc.errors.domain;

public interface ErrorProcessor {

void processError (ErrorGenerated error);

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package tv.codely.mooc.errors.infrastructure;

import tv.codely.mooc.errors.domain.ErrorGenerated;
import tv.codely.mooc.errors.domain.ErrorProcessor;

public class ErrorProcessorSystemErr implements ErrorProcessor {
@Override
public void processError(ErrorGenerated error) {
System.err.println(error.getErrorMessage() + ":" + error.getCause());
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
package tv.codely.mooc.notification.application.create;

import lombok.AllArgsConstructor;
import tv.codely.mooc.errors.domain.ErrorGenerated;
import tv.codely.mooc.notification.domain.TwitterException;
import tv.codely.mooc.notification.domain.TwitterPublisher;
import tv.codely.mooc.video.domain.VideoPublished;
import tv.codely.shared.application.DomainEventSubscriber;
import tv.codely.shared.domain.EventBus;

import static java.util.Arrays.asList;

@AllArgsConstructor
public class SendTweetOnVideoPublished implements DomainEventSubscriber<VideoPublished> {

TwitterPublisher publisher;
EventBus eventBus;

@Override
public Class<VideoPublished> subscribedTo() {
return VideoPublished.class;
}

@Override
public void consume(VideoPublished event) {
try {
publisher.tweet("New video published: " + event.title());
} catch (TwitterException e) {
eventBus.publish(asList(new ErrorGenerated("Error tweeting after video published", e)));
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package tv.codely.mooc.notification.domain;

public class TwitterException extends Exception{

public TwitterException(String message, Throwable cause) {
super(message, cause);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package tv.codely.mooc.notification.domain;

public interface TwitterPublisher {

void tweet (String text) throws TwitterException;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package tv.codely.mooc.notification.infrastructure;

import tv.codely.mooc.notification.domain.TwitterPublisher;

public class FakeTwitterPublisher implements TwitterPublisher {
@Override
public void tweet(String text) {
//Do nothing here
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
package tv.codely.mooc.notification.infrastructure;

import lombok.AllArgsConstructor;
import tv.codely.mooc.notification.domain.TwitterPublisher;
import twitter4j.Twitter;
import twitter4j.TwitterException;
import twitter4j.TwitterFactory;
import twitter4j.conf.ConfigurationBuilder;

@AllArgsConstructor
public class Twitter4JPublisher implements TwitterPublisher {

String consumerKey;
String consumerSecret;
String accessToken;
String accessTokenSecret;

protected Twitter getTwitterInstance() {
ConfigurationBuilder cb = new ConfigurationBuilder();
cb.setOAuthConsumerKey(consumerKey)
.setOAuthConsumerSecret(consumerSecret)
.setOAuthAccessToken(accessToken)
.setOAuthAccessTokenSecret(accessTokenSecret);
TwitterFactory tf = new TwitterFactory(cb.build());
return tf.getInstance();
}

@Override
public void tweet(String text) throws tv.codely.mooc.notification.domain.TwitterException {
tweet(text, getTwitterInstance());
}

private void tweet(String text, Twitter twitterInstance) throws tv.codely.mooc.notification.domain.TwitterException {
try {
twitterInstance.updateStatus(text);
} catch (TwitterException e) {
throw new tv.codely.mooc.notification.domain.TwitterException("Error updating status " + text, e);
}
}
}
Original file line number Diff line number Diff line change
@@ -1,19 +1,28 @@
package tv.codely.mooc.video.infrastructure;

import tv.codely.mooc.errors.application.ProcessErrorGenerated;
import tv.codely.mooc.errors.domain.ErrorProcessor;
import tv.codely.mooc.errors.infrastructure.ErrorProcessorSystemErr;
import tv.codely.mooc.notification.application.create.SendPushToSubscribersOnVideoPublished;
import tv.codely.mooc.notification.application.create.SendTweetOnVideoPublished;
import tv.codely.mooc.notification.domain.TwitterPublisher;
import tv.codely.mooc.notification.infrastructure.FakeTwitterPublisher;
import tv.codely.mooc.video.application.publish.VideoPublisher;
import tv.codely.shared.application.DomainEventSubscriber;
import tv.codely.shared.domain.EventBus;
import tv.codely.shared.infrastructure.bus.ReactorEventBus;

import java.util.Set;

public class VideoPublisherCliController {
public static void main(String[] args) {
final TwitterPublisher twitterPublisher = new FakeTwitterPublisher();
final ErrorProcessor errorProcessor = new ErrorProcessorSystemErr();
final Set<DomainEventSubscriber> subscribers = Set.of(
new SendPushToSubscribersOnVideoPublished()
new SendPushToSubscribersOnVideoPublished(),
new ProcessErrorGenerated(errorProcessor)
);
final EventBus eventBus = new ReactorEventBus(subscribers);
final ReactorEventBus eventBus = new ReactorEventBus(subscribers);
eventBus.addSubscriber(new SendTweetOnVideoPublished(twitterPublisher, eventBus));
final var videoPublisher = new VideoPublisher(eventBus);

final var videoTitle = "\uD83C\uDF89 New YouTube.com/CodelyTV video title";
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
package tv.codely.mooc.errors.application;

import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import tv.codely.mooc.errors.domain.ErrorGenerated;
import tv.codely.mooc.errors.domain.ErrorProcessor;

import static org.junit.jupiter.api.Assertions.*;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;

class ProcessErrorGeneratedTest {

ProcessErrorGenerated processErrorGenerated;

ErrorProcessor errorProcessor;

@BeforeEach
void setUp() {
errorProcessor = mock(ErrorProcessor.class);
processErrorGenerated = new ProcessErrorGenerated(errorProcessor);
}

@Test
void consume() {
ErrorGenerated errorEvent = new ErrorGenerated("error", new IllegalArgumentException(""));

processErrorGenerated.consume(errorEvent);

verify(errorProcessor).processError(errorEvent);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
package tv.codely.mooc.notification.application.create;

import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import tv.codely.mooc.errors.domain.ErrorGenerated;
import tv.codely.mooc.notification.domain.TwitterException;
import tv.codely.mooc.notification.domain.TwitterPublisher;
import tv.codely.mooc.video.domain.VideoPublished;
import tv.codely.shared.domain.EventBus;

import static java.util.Arrays.asList;
import static org.mockito.Mockito.*;

class SendTweetOnVideoPublishedShould {

SendTweetOnVideoPublished sendTweetOnVideoPublished;

TwitterPublisher publisher;
EventBus eventBus;

@BeforeEach
void setUp() {
publisher = mock(TwitterPublisher.class);
eventBus = mock(EventBus.class);
sendTweetOnVideoPublished = new SendTweetOnVideoPublished(publisher, eventBus);
}

@Test
void consume_should_send_text_to_twitter() throws TwitterException {
VideoPublished videoPublished = new VideoPublished("title", "description");

sendTweetOnVideoPublished.consume(videoPublished);

verify(publisher).tweet("New video published: " + videoPublished.title());
}

@Test
void consume_should_send_notify_error_if_exception_tweeting() throws TwitterException {
VideoPublished videoPublished = new VideoPublished("title", "description");
TwitterException exception = new TwitterException("error", new IllegalArgumentException(""));
doThrow(exception).when(publisher).tweet(anyString());

sendTweetOnVideoPublished.consume(videoPublished);

verify(eventBus).publish(asList(new ErrorGenerated("Error tweeting after video published", exception)));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
package tv.codely.mooc.notification.infrastructure;

import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import twitter4j.Twitter;
import twitter4j.TwitterException;

import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.mockito.Mockito.*;

class Twitter4JPublisherShould {

Twitter4JPublisher publisher;

Twitter twitterInstance;

@BeforeEach
void setUp() {
twitterInstance = mock(Twitter.class);
publisher = spy(new Twitter4JPublisher("consumerKey", "consumerSecret", "accessToken", "accessTokenSecret"));
doReturn(twitterInstance).when(publisher).getTwitterInstance();
}

@Test
void post_tweet() throws tv.codely.mooc.notification.domain.TwitterException, TwitterException {
String text = "Text to tweet";

publisher.tweet(text);

verify(twitterInstance).updateStatus(text);
}

@Test
void post_tweet_with_error() throws TwitterException {
String text = "Text to tweet";
doThrow(new TwitterException("Error")).when(twitterInstance).updateStatus(text);

assertThrows(tv.codely.mooc.notification.domain.TwitterException.class, () -> {
publisher.tweet(text);
});
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,10 @@ public ReactorEventBus(final Set<DomainEventSubscriber> subscribers) {
subscribers.forEach(this::registerOnEventBus);
}

public void addSubscriber (DomainEventSubscriber subscriber) {
registerOnEventBus(subscriber);
}

@Override
public void publish(final List<DomainEvent> events) {
events.forEach(this::publish);
Expand Down