In this post, I’ll guide you through my interactions with ChatGPT-4, as it helps write and test the entire New York Times implementation of the news adapter we created in part 1. As a disclaimer, I don’t recommend using this approach if you’re working on sensitive projects or those involving intellectual property. Additionally, never blindly copy, paste, and execute any code generated by AI tools.

New York Times News Adapter

Googling to learn the best way of obtaining news content from New York Times is so 2022. Is there a public API or will I have to write a web scraper? Let ChatGPT figure it out!

Initial prompt

I added some documentation to the NewsAdapter port in an attempt to guide ChatGPT, and then copied what I thought was all relevant code it would need to perform this task.

I am creating an application in golang following the principals of hexagonal architecture. I would like for you to create an NewYorkTime news adapter that fits the following interface.

package ports

import (
	"context"
	"github.com/BaronBonet/content-generator/internal/core> /domain"
)
// NewsAdapter interacts with external news services
type NewsAdapter interface {
	// GetMainArticle finds the main article, the concept > of the main article will be adapter specific.
	GetMainArticle(ctx context.Context) (domain.> NewsArticle, error)
}

package domain

import "time"

type NewsArticle struct {
	Title string
	Body  string
	Date  Date
}

type Date struct {
	Day   int
	Month time.Month
	Year  int
}

This adapter will implement `GetMainArticle`.

ChatGPT responded with what appeared to be functional code (although it did use some depreciated methods) along with instructions on obtaining a NYTimes API key. I created an internal/adapters directory and placed the provided code there.

chatGPT newsAdapter Response

Does it work?

I wanted to test this with my debugger and know if it actually works, so I asked chatGPT to wire it up.

thanks, can you now wire this adapter up for me in a main.go file, so I can test this in my goland debugger

debugger prompt

After changing a few imports in the adapter and adding the main function it wrote to cmd/debugger/main.go code, I tested the code in my goland debugger, and it worked!

chatGPT new york times test

Prompt ChatGPT to write the tests

We’re on a roll, lets finish this off by asking ChatGPT to make a test for this. I’ve noticed it’s better if you send as much information as possible, that’s why I copy/pasted all the adapter code, that ChatGPT wrote (although I did make a few changes).

It works great! Could you now write some test for this > adapter, you should only have to mock this line.

resp, err := http.DefaultClient.Do(req)

package adapters

import (
	"context"
	"encoding/json"
	"errors"
	"fmt"
	"io/ioutil"
	"net/http"
	"time"

	"github.com/BaronBonet/content-generator/internal/core> /domain"
	"github.com/BaronBonet/content-generator/internal/core> /ports"
)

const (
	apiURL = "https://api.nytimes.> com/svc/topstories/v2/home.json?api-key=%s"
)

type nyTimesAdapter struct {
	apiKey string
}

func NewNYTimesNewsAdapter(apiKey string) ports.> NewsAdapter {
	return &nyTimesAdapter{apiKey: apiKey}
}

func (n *nyTimesAdapter) GetMainArticle(ctx context.> Context) (domain.NewsArticle, error) {
	url := fmt.Sprintf(apiURL, n.apiKey)
	req, err := http.NewRequestWithContext(ctx, http.> MethodGet, url, nil)
	if err != nil {
		return domain.NewsArticle{}, err
	}

	resp, err := http.DefaultClient.Do(req)
	if err != nil {
		return domain.NewsArticle{}, err
	}
	defer resp.Body.Close()

	if resp.StatusCode != http.StatusOK {
		return domain.NewsArticle{}, errors.New("failed to>  fetch data from New York Times API")
	}

	body, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		return domain.NewsArticle{}, err
	}

	var apiResponse NYTApiResponse
	err = json.Unmarshal(body, &apiResponse)
	if err != nil {
		return domain.NewsArticle{}, err
	}

	if len(apiResponse.Results) == 0 {
		return domain.NewsArticle{}, errors.New("no > articles found")
	}

	mainArticle := apiResponse.Results[0]
	date, err := time.Parse(time.RFC3339, mainArticle.> PublishedDate)
	if err != nil {
		return domain.NewsArticle{}, err
	}

	return domain.NewsArticle{
		Title: mainArticle.Title,
		Body:  mainArticle.Abstract,
		Date: domain.Date{
			Day:   date.Day(),
			Month: date.Month(),
			Year:  date.Year(),
		},
	}, nil
}

type NYTApiResponse struct {
	Results []NYTArticle `json:"results"`
}

type NYTArticle struct {
	Title         string `json:"title"`
	Abstract      string `json:"abstract"`
	PublishedDate string `json:"published_date"`
}

ChatGPT’s Tests

I’ve included the start and end of the response ChatGPT made the entire test is here. I like how it decided to slightly change the implementation of the adapter, by making the NYTime adapter depend on an httpClient interface, to make it more testable.

ChatGPT Test Response
ChatGPT Test Response Part 2

Conclusion

We’ve now implemented the first of four adapters, with the following workflow:

  • Document the adapter port we want implemented
  • Copy the documented port and any other relevant code to ChatGPT
  • Test that the code ChatGPT wrote works functionally through the debugger. Note: I use this step because ChatGPT sometime hallucinates and uses public methods that don’t exist or do the wrong thing.
  • Write unit tests for the adapter.

I’ll use this same workflow to implement the other 3 adapters.

Current state of the project

.
├── cmd
│   └── debugger
│       └── main.go
├── go.mod
└── internal
    ├── adapters
    │   ├── newsadapter_nytimes.go
    │   └── newsadapter_nytimes_test.go
    └── core
        ├── domain
        │   └── domain.go
        ├── ports
        │   └── driven.go
        │   └── infrastructure.go
        │   └── service.go
        └── service
            └── service.go
            └── service_test.go