In this particular ideal case, you need to make sure that your cosines are "commensurate" with your domain. I will explain what this means.
By using np.linspace with endpoint=True (which is its default value) you are sampling the boundary value twice. This is because the FFT models functions as being periodic. For example if your domain has length $T$, then the FFT acts as if $y(t)=y(t+T)$ for all $t$. Your domain should be $[0,T)$, i.e. excluding one endpoint. If you include both endpoints, like in $[0,T]$, you will sample the point $t=0$ twice. This is because $y(0)=y(T)$, again because of periodicity. If you do this, the function is not 'commensurate' with your domain, which just means that it does not fit nicely. You can solve this by having endpoint=False
Here is some code that draw the spectrum of a single sine wave. First I plot it using endpoint=True and then using endpoint=False, the latter of which gives the correct result.
import numpy as np
import matplotlib.pyplot as plt
N = 8
t = np.linspace(0, 2*np.pi, endpoint=False, num=N)
y = np.sin(t)
fig, axes = plt.subplots(ncols=2, figsize=(8,3), dpi=200)
axes[0].plot(t, y, 'o-')
axes[0].set_xlabel('$t$')
axes[0].set_ylabel('$y$')
yfft = np.fft.fft(y)
omega = np.fft.fftfreq(N, d=(t[1]-t[0]))
yfft = np.fft.fftshift(yfft)
omega = np.fft.fftshift(omega)
axes[1].plot(omega, np.abs(yfft)**2, 'o-')
axes[1].set_xlabel('$\omega$')
axes[1].set_ylabel(r'$\hat y$')
plt.tight_layout()
plt.show()
Here the function is incommensurate, with endpoint=True

Here the function is commensurate, with endpoint=False

Notice how nicely the second FFT behaves. Indeed, all the entries that you want to be zero are actually zero.
To drive my point home even more, I tile my function $y$ 2 times, as if it were periodic. Here you see why you get the slightly off Fourier transform.

One more important question: how do you solve this in the real world, where you have no control over your domain? Well, here's where things get ugly and you have to consider your options. You can ensure you sample enough periods, have enough sample points and you could also reduce edge-effects by using a window. You are using all three of these options and it shows. If it was a sample of real data, it would be really good. Even though your functions are not commensurate.